diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58381df99..41c85cb62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: '11' + java-version: '17' - name: Cache local Maven repository uses: actions/cache@v2 @@ -45,10 +45,10 @@ jobs: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: '11' + java-version: '17' - name: Cache local Maven repository uses: actions/cache@v2 @@ -71,10 +71,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: '11' + java-version: '17' - name: Cache local Maven repository uses: actions/cache@v2 diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt new file mode 100644 index 000000000..b9fc26420 --- /dev/null +++ b/ThirdPartyNotices.txt @@ -0,0 +1,437 @@ +java-debug + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +Do Not Translate or Localize + +This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. + +1. ReactiveX/RxJava (https://github.com/ReactiveX/RxJava) +2. reactive-streams/reactive-streams-jvm (https://github.com/reactive-streams/reactive-streams-jvm) +3. apache/commons-io (https://github.com/apache/commons-io) + + +%% ReactiveX/RxJava NOTICES AND INFORMATION BEGIN HERE +========================================= + + 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. +========================================= +END OF ReactiveX/RxJava NOTICES AND INFORMATION + +%% reactive-streams/reactive-streams-jvm NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT No Attribution + +Copyright 2014 Reactive Streams + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF reactive-streams/reactive-streams-jvm NOTICES AND INFORMATION + +%% apache/commons-io NOTICES AND INFORMATION BEGIN HERE +========================================= + + 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. +========================================= +END OF apache/commons-io NOTICES AND INFORMATION \ No newline at end of file diff --git a/com.microsoft.java.debug.core/.classpath b/com.microsoft.java.debug.core/.classpath index f0257c5a5..9ba41a249 100644 --- a/com.microsoft.java.debug.core/.classpath +++ b/com.microsoft.java.debug.core/.classpath @@ -13,7 +13,7 @@ - + diff --git a/com.microsoft.java.debug.core/mvnw b/com.microsoft.java.debug.core/mvnw new file mode 100755 index 000000000..e96ccd5fb --- /dev/null +++ b/com.microsoft.java.debug.core/mvnw @@ -0,0 +1,227 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/com.microsoft.java.debug.core/mvnw.cmd b/com.microsoft.java.debug.core/mvnw.cmd new file mode 100644 index 000000000..019bd74d7 --- /dev/null +++ b/com.microsoft.java.debug.core/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml index 5b6f0c2c3..6328bc290 100644 --- a/com.microsoft.java.debug.core/pom.xml +++ b/com.microsoft.java.debug.core/pom.xml @@ -2,16 +2,47 @@ 4.0.0 - - com.microsoft.java - java-debug-parent - 0.32.0 - + ch.epfl.scala com.microsoft.java.debug.core jar ${base.name} :: Debugger Core + The Java Debug Server is an implementation of Visual Studio Code (VSCode) Debug Protocol. It can be used in Visual Studio Code to debug Java programs. + https://github.com/scalacenter/java-debug + 0.38.0+1 + Java Debug Server for Visual Studio Code + UTF-8 + ${basedir}/../ + + + + Eclipse Public License 1.0 + https://github.com/Microsoft/java-debug/blob/master/LICENSE.txt + repo + + + + + + adpi2 + Adrien Piquerez + adrien.piquerez@gmail.com + "https://github.com/adpi2/" + + + + + ch.epfl.scala + https://scala.epfl.ch/ + + + + scm:git:git://github.com/scalacenter/java-debug.git + scm:git:ssh://github.com:scalacenter/java-debug.git + https://github.com/scalacenter/java-debug/tree/main + + target target/classes @@ -22,19 +53,67 @@ org.apache.maven.plugins maven-failsafe-plugin + 2.15 + + + + integration-tests + + integration-test + verify + + + + ${failsafeArgLine} + + ${skip.integration.tests} + + + org.apache.maven.plugins maven-compiler-plugin 3.7.0 - 1.8 - 1.8 + 11 + 11 org.apache.maven.plugins maven-checkstyle-plugin + 3.1.0 + + + com.puppycrawl.tools + checkstyle + 8.29 + + + com.github.sevntu-checkstyle + sevntu-checkstyle-maven-plugin + 1.24.1 + + + + ${checkstyleDir}/check_style.xml + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + @@ -47,22 +126,22 @@ com.google.code.gson gson - 2.7 + 2.8.9 io.reactivex.rxjava2 rxjava - 2.1.1 + 2.2.21 org.reactivestreams reactive-streams - 1.0.0 + 1.0.4 commons-io commons-io - 2.5 + 2.11.0 @@ -79,7 +158,7 @@ - + default-tools.jar (,9) @@ -94,5 +173,61 @@ + + release + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.1 + + + attach-javadocs + + jar + + + + + none + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java index 8b9db7301..3c625b8a1 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java @@ -148,4 +148,10 @@ public IEventHub getEventHub() { public VirtualMachine getVM() { return vm; } + + @Override + public IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, + int hitCount) { + return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount); + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java index 24138fef1..03780c2d9 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java @@ -36,7 +36,7 @@ public interface IDebugSession { void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters); - // TODO: createFunctionBreakpoint + IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount); Process process(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java new file mode 100644 index 000000000..668884567 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java @@ -0,0 +1,33 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug.core; + +import java.util.concurrent.CompletableFuture; + +public interface IMethodBreakpoint extends IDebugResource { + String methodName(); + + String className(); + + int getHitCount(); + + String getCondition(); + + void setHitCount(int hitCount); + + void setCondition(String condition); + + CompletableFuture install(); + + Object getProperty(Object key); + + void putProperty(Object key, Object value); +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/LaunchException.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/LaunchException.java new file mode 100644 index 000000000..6c1e1a513 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/LaunchException.java @@ -0,0 +1,51 @@ +/******************************************************************************* +* Copyright (c) 2018-2021 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.connect.VMStartException; + +/** + * Extends {@link VMStartException} to provide more detail about the failed process + * from before it is destroyed. + */ +public class LaunchException extends VMStartException { + + boolean exited; + int exitStatus; + String stdout; + String stderr; + + public LaunchException(String message, Process process, boolean exited, int exitStatus, String stdout, String stderr) { + super(message, process); + this.exited = exited; + this.exitStatus = exitStatus; + this.stdout = stdout; + this.stderr = stderr; + } + + public boolean isExited() { + return exited; + } + + public int getExitStatus() { + return exitStatus; + } + + public String getStdout() { + return stdout; + } + + public String getStderr() { + return stderr; + } + +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java new file mode 100644 index 000000000..82c5b75e2 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java @@ -0,0 +1,258 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.ReferenceType; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.ThreadDeathEvent; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.MethodEntryRequest; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; + +public class MethodBreakpoint implements IMethodBreakpoint, IEvaluatableBreakpoint { + + private VirtualMachine vm; + private IEventHub eventHub; + private String className; + private String functionName; + private String condition; + private int hitCount; + + private HashMap propertyMap = new HashMap<>(); + private Object compiledConditionalExpression = null; + private Map compiledExpressions = new ConcurrentHashMap<>(); + + private List requests = new ArrayList<>(); + private List subscriptions = new ArrayList<>(); + + public MethodBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, String functionName, + String condition, int hitCount) { + Objects.requireNonNull(vm); + Objects.requireNonNull(eventHub); + Objects.requireNonNull(className); + Objects.requireNonNull(functionName); + this.vm = vm; + this.eventHub = eventHub; + this.className = className; + this.functionName = functionName; + this.condition = condition; + this.hitCount = hitCount; + } + + @Override + public List requests() { + return requests; + } + + @Override + public List subscriptions() { + return subscriptions; + } + + @Override + public void close() throws Exception { + try { + vm.eventRequestManager().deleteEventRequests(requests()); + } catch (VMDisconnectedException ex) { + // ignore since removing breakpoints is meaningless when JVM is terminated. + } + subscriptions().forEach(Disposable::dispose); + requests.clear(); + subscriptions.clear(); + } + + @Override + public boolean containsEvaluatableExpression() { + return containsConditionalExpression() || containsLogpointExpression(); + } + + @Override + public boolean containsConditionalExpression() { + return StringUtils.isNotBlank(getCondition()); + } + + @Override + public boolean containsLogpointExpression() { + return false; + } + + @Override + public String getCondition() { + return condition; + } + + @Override + public void setCondition(String condition) { + this.condition = condition; + setCompiledConditionalExpression(null); + compiledExpressions.clear(); + } + + @Override + public String getLogMessage() { + return null; + } + + @Override + public void setLogMessage(String logMessage) { + // for future implementation + } + + @Override + public void setCompiledConditionalExpression(Object compiledExpression) { + this.compiledConditionalExpression = compiledExpression; + } + + @Override + public Object getCompiledConditionalExpression() { + return compiledConditionalExpression; + } + + @Override + public void setCompiledLogpointExpression(Object compiledExpression) { + // for future implementation + } + + @Override + public Object getCompiledLogpointExpression() { + return null; + } + + @Override + public void setCompiledExpression(long threadId, Object compiledExpression) { + compiledExpressions.put(threadId, compiledExpression); + } + + @Override + public Object getCompiledExpression(long threadId) { + return compiledExpressions.get(threadId); + } + + @Override + public int getHitCount() { + return hitCount; + } + + @Override + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + Observable.fromIterable(this.requests()) + .filter(request -> request instanceof MethodEntryRequest) + .subscribe(request -> { + request.addCountFilter(hitCount); + request.enable(); + }); + } + + @Override + public CompletableFuture install() { + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent) + .subscribe(debugEvent -> { + ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread(); + compiledExpressions.remove(deathThread.uniqueID()); + }); + + subscriptions.add(subscription); + + // It's possible that different class loaders create new class with the same + // name. + // Here to listen to future class prepare events to handle such case. + ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); + classPrepareRequest.addClassFilter(className); + classPrepareRequest.enable(); + requests.add(classPrepareRequest); + + CompletableFuture future = new CompletableFuture<>(); + subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent + && (classPrepareRequest.equals(debugEvent.event.request()))) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + Optional createdRequest = createMethodEntryRequest(event.referenceType()); + if (createdRequest.isPresent()) { + MethodEntryRequest methodEntryRequest = createdRequest.get(); + requests.add(methodEntryRequest); + if (!future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + } + }); + subscriptions.add(subscription); + + List types = vm.classesByName(className); + for (ReferenceType type : types) { + Optional createdRequest = createMethodEntryRequest(type); + if (createdRequest.isPresent()) { + MethodEntryRequest methodEntryRequest = createdRequest.get(); + requests.add(methodEntryRequest); + if (!future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + } + } + return future; + } + + private Optional createMethodEntryRequest(ReferenceType type) { + return type.methodsByName(functionName).stream().findFirst().map(method -> { + MethodEntryRequest request = vm.eventRequestManager().createMethodEntryRequest(); + + request.addClassFilter(type); + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + if (hitCount > 0) { + request.addCountFilter(hitCount); + } + request.enable(); + return request; + }); + } + + @Override + public Object getProperty(Object key) { + return propertyMap.get(key); + } + + @Override + public void putProperty(Object key, Object value) { + propertyMap.put(key, value); + } + + @Override + public String methodName() { + return functionName; + } + + @Override + public String className() { + return className; + } + +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java index bc6297b73..aed2ad816 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -24,6 +24,7 @@ import java.util.logging.Logger; import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; import com.microsoft.java.debug.core.IWatchpoint; public class BreakpointManager implements IBreakpointManager { @@ -34,6 +35,7 @@ public class BreakpointManager implements IBreakpointManager { private List breakpoints; private Map> sourceToBreakpoints; private Map watchpoints; + private Map methodBreakpoints; private AtomicInteger nextBreakpointId = new AtomicInteger(1); /** @@ -44,6 +46,7 @@ public BreakpointManager(Logger logger) { this.breakpoints = Collections.synchronizedList(new ArrayList<>(5)); this.sourceToBreakpoints = new HashMap<>(); this.watchpoints = new HashMap<>(); + this.methodBreakpoints = new HashMap<>(); } @Override @@ -208,4 +211,61 @@ private String getWatchpointKey(IWatchpoint watchpoint) { public IWatchpoint[] getWatchpoints() { return this.watchpoints.values().stream().filter(wp -> wp != null).toArray(IWatchpoint[]::new); } + + @Override + public IMethodBreakpoint[] getMethodBreakpoints() { + return this.methodBreakpoints.values().stream().filter(Objects::nonNull).toArray(IMethodBreakpoint[]::new); + } + + @Override + public IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] breakpoints) { + List result = new ArrayList<>(); + List toAdds = new ArrayList<>(); + List toRemoves = new ArrayList<>(); + + Set visitedKeys = new HashSet<>(); + for (IMethodBreakpoint change : breakpoints) { + if (change == null) { + result.add(change); + continue; + } + + String key = getMethodBreakpointKey(change); + IMethodBreakpoint cache = methodBreakpoints.get(key); + if (cache != null) { + visitedKeys.add(key); + result.add(cache); + } else { + toAdds.add(change); + result.add(change); + } + } + + for (IMethodBreakpoint cache : methodBreakpoints.values()) { + if (!visitedKeys.contains(getMethodBreakpointKey(cache))) { + toRemoves.add(cache); + } + } + + for (IMethodBreakpoint toRemove : toRemoves) { + try { + // Destroy the method breakpoint on the debugee VM. + toRemove.close(); + this.methodBreakpoints.remove(getMethodBreakpointKey(toRemove)); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove the method breakpoint exception: %s", e.toString()), e); + } + } + + for (IMethodBreakpoint toAdd : toAdds) { + toAdd.putProperty("id", this.nextBreakpointId.getAndIncrement()); + this.methodBreakpoints.put(getMethodBreakpointKey(toAdd), toAdd); + } + + return result.toArray(new IMethodBreakpoint[0]); + } + + private String getMethodBreakpointKey(IMethodBreakpoint breakpoint) { + return breakpoint.className() + "#" + breakpoint.methodName(); + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java index 5bdd5eef7..d4b9069fd 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java @@ -31,12 +31,14 @@ import com.microsoft.java.debug.core.adapter.handler.InitializeRequestHandler; import com.microsoft.java.debug.core.adapter.handler.InlineValuesRequestHandler; import com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.ProcessIdHandler; import com.microsoft.java.debug.core.adapter.handler.RefreshVariablesHandler; import com.microsoft.java.debug.core.adapter.handler.RestartFrameHandler; import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetBreakpointsRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetDataBreakpointsRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetExceptionBreakpointsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetFunctionBreakpointsRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetVariableRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SourceRequestHandler; import com.microsoft.java.debug.core.adapter.handler.StackTraceRequestHandler; @@ -125,10 +127,11 @@ private void initialize() { registerHandlerForDebug(new SetDataBreakpointsRequestHandler(logger)); registerHandlerForDebug(new InlineValuesRequestHandler(logger)); registerHandlerForDebug(new RefreshVariablesHandler()); - + registerHandlerForDebug(new ProcessIdHandler()); + registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler(logger)); // NO_DEBUG mode only registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler(logger)); - + registerHandlerForNoDebug(new ProcessIdHandler()); } private void registerHandlerForDebug(IDebugRequestHandler handler) { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java index a014f04d6..85ce5ace3 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java @@ -55,6 +55,9 @@ public class DebugAdapterContext implements IDebugAdapterContext { private Path classpathJar = null; private Path argsfile = null; + private long shellProcessId = -1; + private long processId = -1; + private IdCollection sourceReferences = new IdCollection<>(); private RecyclableObjectPool recyclableIdPool = new RecyclableObjectPool<>(); private IVariableFormatter variableFormatter = VariableFormatterFactory.createVariableFormatter(); @@ -329,4 +332,24 @@ public IBreakpointManager getBreakpointManager() { public IStepResultManager getStepResultManager() { return stepResultManager; } + + @Override + public long getProcessId() { + return this.processId; + } + + @Override + public long getShellProcessId() { + return this.shellProcessId; + } + + @Override + public void setProcessId(long processId) { + this.processId = processId; + } + + @Override + public void setShellProcessId(long shellProcessId) { + this.shellProcessId = shellProcessId; + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java index 9ba609221..196714d52 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java @@ -12,6 +12,7 @@ package com.microsoft.java.debug.core.adapter; import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; import com.microsoft.java.debug.core.IWatchpoint; public interface IBreakpointManager { @@ -69,4 +70,23 @@ public interface IBreakpointManager { * Returns all registered watchpoints. */ IWatchpoint[] getWatchpoints(); + + /** + * Returns all the registered method breakpoints. + */ + IMethodBreakpoint[] getMethodBreakpoints(); + + /** + * Update the method breakpoints list. If the requested method breakpoints + * already registered in the breakpoint + * manager, reuse the cached one. Otherwise register the requested method + * breakpoints as a new method breakpoints. + * Besides, delete those not existed any more. + * + * @param methodBreakpoints + * the method breakpoints requested by client + * @return the full registered method breakpoints list + */ + IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] methodBreakpoints); + } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java index 4f843fd09..a1b21ecee 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java @@ -128,4 +128,12 @@ public interface IDebugAdapterContext { IBreakpointManager getBreakpointManager(); IStepResultManager getStepResultManager(); + + void setShellProcessId(long shellProcessId); + + long getShellProcessId(); + + void setProcessId(long processId); + + long getProcessId(); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepFilterProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepFilterProvider.java new file mode 100644 index 000000000..faa92f7b8 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepFilterProvider.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2020 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.protocol.Requests; +import com.sun.jdi.Method; + +public interface IStepFilterProvider extends IProvider { + boolean skip(Method method, Requests.StepFilters filters); +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java index 6b384c628..3d823df91 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java @@ -129,7 +129,7 @@ public void stop() { } private void monitor(InputStream input, PublishSubject subject) { - BufferedReader reader = new BufferedReader(new InputStreamReader(input, encoding)); + BufferedReader reader = new BufferedReader(encoding == null ? new InputStreamReader(input) : new InputStreamReader(input, encoding)); final int BUFFERSIZE = 4096; char[] buffer = new char[BUFFERSIZE]; while (true) { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepFilterProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepFilterProvider.java new file mode 100644 index 000000000..f55b85fff --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepFilterProvider.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.protocol.Requests; +import com.sun.jdi.Method; +import org.apache.commons.lang3.ArrayUtils; + +public class StepFilterProvider implements IStepFilterProvider { + @Override + public boolean skip(Method method, Requests.StepFilters filters) { + if (!isConfigured(filters)) { + return false; + } + return (filters.skipStaticInitializers && method.isStaticInitializer()) + || (filters.skipSynthetics && method.isSynthetic()) + || (filters.skipConstructors && method.isConstructor()); + } + + private boolean isConfigured(Requests.StepFilters filters) { + if (filters == null) { + return false; + } + return ArrayUtils.isNotEmpty(filters.allowClasses) || ArrayUtils.isNotEmpty(filters.skipClasses) + || ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors + || filters.skipStaticInitializers || filters.skipSynthetics; + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java index 9f660644a..eba7d5153 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java @@ -11,8 +11,8 @@ package com.microsoft.java.debug.core.adapter.handler; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -46,7 +46,7 @@ public CompletableFuture handle(Command command, Arguments arguments, // completions should be illegal when frameId is zero, it is sent when the program is running, while during running we cannot resolve // the completion candidates if (completionsArgs.frameId == 0) { - response.body = new ArrayList<>(); + response.body = new Responses.CompletionsResponseBody(Collections.emptyList()); return CompletableFuture.completedFuture(response); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java index 8d33d04bd..149c45d72 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2018 Microsoft Corporation and others. +* Copyright (c) 2018-2022 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -12,6 +12,7 @@ package com.microsoft.java.debug.core.adapter.handler; import java.util.logging.Logger; +import java.util.Optional; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.protocol.Messages.Response; @@ -32,6 +33,11 @@ public void destroyDebugSession(Command command, Arguments arguments, Response r Process debuggeeProcess = context.getDebuggeeProcess(); if (debuggeeProcess != null && disconnectArguments.terminateDebuggee) { debuggeeProcess.destroy(); + } else if (context.getProcessId() > 0 && disconnectArguments.terminateDebuggee) { + Optional debuggeeHandle = ProcessHandle.of(context.getProcessId()); + if (debuggeeHandle.isPresent()) { + debuggeeHandle.get().destroy(); + } } } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java index be78bac64..bd0930931 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017-2020 Microsoft Corporation and others. +* Copyright (c) 2017-2021 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -14,7 +14,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; @@ -106,10 +105,8 @@ public CompletableFuture handle(Command command, Arguments arguments, indexedVariables = ((IntegerValue) sizeValue).value(); } } - } catch (CancellationException | IllegalArgumentException | InterruptedException - | ExecutionException | UnsupportedOperationException e) { - logger.log(Level.INFO, - String.format("Failed to get the logical size for the type %s.", value.type().name()), e); + } catch (Exception e) { + logger.log(Level.INFO, "Failed to get the logical size of the variable", e); } } int referenceId = 0; @@ -117,20 +114,49 @@ public CompletableFuture handle(Command command, Arguments arguments, referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy); } - String valueString = variableFormatter.valueToString(value, options); + boolean hasErrors = false; + String valueString = null; + try { + valueString = variableFormatter.valueToString(value, options); + } catch (OutOfMemoryError e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to convert the value of a large object to a string", e); + valueString = ""; + } catch (Exception e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to resolve the variable value", e); + valueString = ""; + } + String detailsString = null; - if (sizeValue != null) { + if (hasErrors) { + // If failed to resolve the variable value, skip the details info as well. + } else if (sizeValue != null) { detailsString = "size=" + variableFormatter.valueToString(sizeValue, options); } else if (DebugSettings.getCurrent().showToString) { - detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine); + try { + detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine); + } catch (OutOfMemoryError e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e); + detailsString = ""; + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value", e); + detailsString = ""; + } } if ("clipboard".equals(evalArguments.context) && detailsString != null) { response.body = new Responses.EvaluateResponseBody(detailsString, -1, "String", 0); } else { + String typeString = ""; + try { + typeString = variableFormatter.typeToString(value == null ? null : value.type(), options); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to resolve the variable type", e); + typeString = ""; + } response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString, - referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options), - Math.max(indexedVariables, 0)); + referenceId, typeString, Math.max(indexedVariables, 0)); } return response; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java index 4733170eb..ae92f356f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java @@ -62,6 +62,7 @@ public CompletableFuture handle(Requests.Command command, Req caps.exceptionBreakpointFilters = exceptionFilters; caps.supportsExceptionInfoRequest = true; caps.supportsDataBreakpoints = true; + caps.supportsFunctionBreakpoints = true; caps.supportsClipboardContext = true; response.body = caps; return CompletableFuture.completedFuture(response); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java index c5f6bd611..99c1a8f04 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java @@ -16,7 +16,6 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -38,6 +37,7 @@ import com.microsoft.java.debug.core.DebugSettings; import com.microsoft.java.debug.core.DebugUtility; import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.LaunchException; import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -91,23 +91,21 @@ protected CompletableFuture handleLaunchCommand(Arguments arguments, R "Failed to launch debuggee VM. Missing mainClass or modulePaths/classPaths options in launch configuration.", ErrorCode.ARGUMENT_MISSING); } - if (StringUtils.isBlank(launchArguments.encoding)) { - context.setDebuggeeEncoding(StandardCharsets.UTF_8); - } else { + if (StringUtils.isNotBlank(launchArguments.encoding)) { if (!Charset.isSupported(launchArguments.encoding)) { throw AdapterUtils.createCompletionException( "Failed to launch debuggee VM. 'encoding' options in the launch configuration is not recognized.", ErrorCode.INVALID_ENCODING); } context.setDebuggeeEncoding(Charset.forName(launchArguments.encoding)); + if (StringUtils.isBlank(launchArguments.vmArgs)) { + launchArguments.vmArgs = String.format("-Dfile.encoding=%s", context.getDebuggeeEncoding().name()); + } else { + // if vmArgs already has the file.encoding settings, duplicate options for jvm will not cause an error, the right most value wins + launchArguments.vmArgs = String.format("%s -Dfile.encoding=%s", launchArguments.vmArgs, context.getDebuggeeEncoding().name()); + } } - if (StringUtils.isBlank(launchArguments.vmArgs)) { - launchArguments.vmArgs = String.format("-Dfile.encoding=%s", context.getDebuggeeEncoding().name()); - } else { - // if vmArgs already has the file.encoding settings, duplicate options for jvm will not cause an error, the right most value wins - launchArguments.vmArgs = String.format("%s -Dfile.encoding=%s", launchArguments.vmArgs, context.getDebuggeeEncoding().name()); - } context.setLaunchMode(launchArguments.noDebug ? LaunchMode.NO_DEBUG : LaunchMode.DEBUG); activeLaunchHandler.preLaunch(launchArguments, context); @@ -141,6 +139,17 @@ protected CompletableFuture handleLaunchCommand(Arguments arguments, R } return launch(launchArguments, response, context).thenCompose(res -> { + long processId = context.getProcessId(); + long shellProcessId = context.getShellProcessId(); + if (context.getDebuggeeProcess() != null) { + processId = context.getDebuggeeProcess().pid(); + } + + // If processId or shellProcessId exist, send a notification to client. + if (processId > 0 || shellProcessId > 0) { + context.getProtocolServer().sendEvent(new Events.ProcessIdNotification(processId, shellProcessId)); + } + LaunchUtils.releaseTempLaunchFile(context.getClasspathJar()); LaunchUtils.releaseTempLaunchFile(context.getArgsfile()); if (res.success) { @@ -249,6 +258,22 @@ protected CompletableFuture launch(LaunchArguments launchArguments, Re .subscribe((event) -> context.getProtocolServer().sendEvent(event)); debuggeeConsole.start(); resultFuture.complete(response); + } catch (LaunchException e) { + if (StringUtils.isNotBlank(e.getStdout())) { + OutputEvent event = convertToOutputEvent(e.getStdout(), Category.stdout, context); + context.getProtocolServer().sendEvent(event); + } + if (StringUtils.isNotBlank(e.getStderr())) { + OutputEvent event = convertToOutputEvent(e.getStderr(), Category.stderr, context); + context.getProtocolServer().sendEvent(event); + } + + resultFuture.completeExceptionally( + new DebugException( + String.format("Failed to launch debuggee VM. Reason: %s", e.getMessage()), + ErrorCode.LAUNCH_FAILURE.getId() + ) + ); } catch (IOException | IllegalConnectorArgumentsException | VMStartException e) { resultFuture.completeExceptionally( new DebugException( diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java index 17a4f7359..ca7d69a99 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2021 Microsoft Corporation and others. +* Copyright (c) 2021-2022 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,28 +11,39 @@ package com.microsoft.java.debug.core.adapter.handler; +import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.adapter.AdapterUtils; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.SystemUtils; public class LaunchUtils { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); private static Set tempFilesInUse = new HashSet<>(); /** @@ -73,11 +84,11 @@ public static synchronized Path generateClasspathJar(String[] classPaths) throws public static synchronized Path generateArgfile(String[] classPaths, String[] modulePaths) throws IOException { String argfile = ""; if (ArrayUtils.isNotEmpty(classPaths)) { - argfile = "-classpath \"" + String.join(File.pathSeparator, classPaths) + "\""; + argfile = "-cp \"" + String.join(File.pathSeparator, classPaths) + "\""; } if (ArrayUtils.isNotEmpty(modulePaths)) { - argfile = " --module-path \"" + String.join(File.pathSeparator, modulePaths) + "\""; + argfile += " --module-path \"" + String.join(File.pathSeparator, modulePaths) + "\""; } argfile = argfile.replace("\\", "\\\\"); @@ -102,6 +113,178 @@ public static void releaseTempLaunchFile(Path tempFile) { } } + public static ProcessHandle findJavaProcessInTerminalShell(long shellPid, String javaCommand, int timeout/*ms*/) { + ProcessHandle shellProcess = ProcessHandle.of(shellPid).orElse(null); + if (shellProcess != null) { + int retry = 0; + final int INTERVAL = 20/*ms*/; + final int maxRetries = timeout / INTERVAL; + final boolean isCygwinShell = isCygwinShell(shellProcess.info().command().orElse(null)); + while (retry <= maxRetries) { + Optional subProcessHandle = shellProcess.descendants().filter(proc -> { + String command = proc.info().command().orElse(""); + return Objects.equals(command, javaCommand) || command.endsWith("\\java.exe") || command.endsWith("/java"); + }).findFirst(); + + if (subProcessHandle.isPresent()) { + if (retry > 0) { + logger.info("Retried " + retry + " times to find Java subProcess."); + } + logger.info("shellPid: " + shellPid + ", javaPid: " + subProcessHandle.get().pid()); + return subProcessHandle.get(); + } else if (isCygwinShell) { + long javaPid = findJavaProcessByCygwinPsCommand(shellProcess, javaCommand); + if (javaPid > 0) { + if (retry > 0) { + logger.info("Retried " + retry + " times to find Java subProcess."); + } + logger.info("[Cygwin Shell] shellPid: " + shellPid + ", javaPid: " + javaPid); + return ProcessHandle.of(javaPid).orElse(null); + } + } + + retry++; + if (retry > maxRetries) { + break; + } + + try { + Thread.sleep(INTERVAL); + } catch (InterruptedException e) { + // do nothing + } + } + + logger.info("Retried " + retry + " times but failed to find Java subProcess of shell pid " + shellPid); + } + + return null; + } + + private static long findJavaProcessByCygwinPsCommand(ProcessHandle shellProcess, String javaCommand) { + String psCommand = detectPsCommandPath(shellProcess.info().command().orElse(null)); + if (psCommand == null) { + return -1; + } + + BufferedReader psReader = null; + List psProcs = new ArrayList<>(); + List javaCandidates = new ArrayList<>(); + try { + String[] headers = null; + int pidIndex = -1; + int ppidIndex = -1; + int winpidIndex = -1; + String line; + String javaExeName = Paths.get(javaCommand).toFile().getName().replaceFirst("\\.exe$", ""); + + Process p = Runtime.getRuntime().exec(new String[] {psCommand, "-l"}); + psReader = new BufferedReader(new InputStreamReader(p.getInputStream())); + /** + * Here is a sample output when running ps command in Cygwin/MINGW64 shell. + * PID PPID PGID WINPID TTY UID STIME COMMAND + * 1869 1 1869 7852 cons2 4096 15:29:27 /usr/bin/bash + * 2271 1 2271 30820 cons4 4096 19:38:30 /usr/bin/bash + * 1812 1 1812 21540 cons1 4096 15:05:03 /usr/bin/bash + * 2216 1 2216 11328 cons3 4096 19:38:18 /usr/bin/bash + * 1720 1 1720 5404 cons0 4096 13:46:42 /usr/bin/bash + * 2269 2216 2269 6676 cons3 4096 19:38:21 /c/Program Files/Microsoft/jdk-11.0.14.9-hotspot/bin/java + * 1911 1869 1869 29708 cons2 4096 15:29:31 /c/Program Files/nodejs/node + * 2315 2271 2315 18064 cons4 4096 19:38:34 /usr/bin/ps + */ + while ((line = psReader.readLine()) != null) { + String[] cols = line.strip().split("\\s+"); + if (headers == null) { + headers = cols; + pidIndex = ArrayUtils.indexOf(headers, "PID"); + ppidIndex = ArrayUtils.indexOf(headers, "PPID"); + winpidIndex = ArrayUtils.indexOf(headers, "WINPID"); + if (pidIndex < 0 || ppidIndex < 0 || winpidIndex < 0) { + logger.warning("Failed to find Java process because ps command is not the standard Cygwin ps command."); + return -1; + } + } else if (cols.length >= headers.length) { + long pid = Long.parseLong(cols[pidIndex]); + long ppid = Long.parseLong(cols[ppidIndex]); + long winpid = Long.parseLong(cols[winpidIndex]); + PsProcess process = new PsProcess(pid, ppid, winpid); + psProcs.add(process); + if (cols[cols.length - 1].endsWith("/" + javaExeName) || cols[cols.length - 1].endsWith("/java")) { + javaCandidates.add(process); + } + } + } + } catch (Exception err) { + logger.log(Level.WARNING, "Failed to find Java process by Cygwin ps command.", err); + } finally { + if (psReader != null) { + try { + psReader.close(); + } catch (IOException e) { + // ignore + } + } + } + + if (!javaCandidates.isEmpty()) { + Set descendantWinpids = shellProcess.descendants().map(proc -> proc.pid()).collect(Collectors.toSet()); + long shellWinpid = shellProcess.pid(); + for (PsProcess javaCandidate: javaCandidates) { + if (descendantWinpids.contains(javaCandidate.winpid)) { + return javaCandidate.winpid; + } + + for (PsProcess psProc : psProcs) { + if (javaCandidate.ppid != psProc.pid) { + continue; + } + + if (descendantWinpids.contains(psProc.winpid) || psProc.winpid == shellWinpid) { + return javaCandidate.winpid; + } + + break; + } + } + } + + return -1; + } + + private static boolean isCygwinShell(String shellPath) { + if (!SystemUtils.IS_OS_WINDOWS || shellPath == null) { + return false; + } + + String lowerShellPath = shellPath.toLowerCase(); + return lowerShellPath.endsWith("git\\bin\\bash.exe") + || lowerShellPath.endsWith("git\\usr\\bin\\bash.exe") + || lowerShellPath.endsWith("mintty.exe") + || lowerShellPath.endsWith("cygwin64\\bin\\bash.exe") + || (lowerShellPath.endsWith("bash.exe") && detectPsCommandPath(shellPath) != null) + || (lowerShellPath.endsWith("sh.exe") && detectPsCommandPath(shellPath) != null); + } + + private static String detectPsCommandPath(String shellPath) { + if (shellPath == null) { + return null; + } + + Path psPath = Paths.get(shellPath, "..\\ps.exe"); + if (!Files.exists(psPath)) { + psPath = Paths.get(shellPath, "..\\..\\usr\\bin\\ps.exe"); + if (!Files.exists(psPath)) { + psPath = null; + } + } + + if (psPath == null) { + return null; + } + + return psPath.normalize().toString(); + } + private static Path tmpdir = null; private static synchronized Path getTmpDir() throws IOException { @@ -156,4 +339,16 @@ private static String getMd5(String input) { return Integer.toString(input.hashCode(), Character.MAX_RADIX); } } + + private static class PsProcess { + long pid; + long ppid; + long winpid; + + public PsProcess(long pid, long ppid, long winpid) { + this.pid = pid; + this.ppid = ppid; + this.winpid = winpid; + } + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java index 3ebcadd4b..17e433373 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. +* Copyright (c) 2017-2022 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -24,6 +24,7 @@ import org.apache.commons.lang3.SystemUtils; import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.DebugSession; import com.microsoft.java.debug.core.DebugUtility; @@ -44,6 +45,7 @@ import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; import com.microsoft.java.debug.core.protocol.Requests.RunInTerminalRequestArguments; +import com.microsoft.java.debug.core.protocol.Responses.RunInTerminalResponseBody; import com.sun.jdi.VirtualMachine; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.IllegalConnectorArgumentsException; @@ -53,7 +55,6 @@ public class LaunchWithDebuggingDelegate extends AbstractLaunchDelegate { private static final int ATTACH_TERMINAL_TIMEOUT = 20 * 1000; - private static final String TERMINAL_TITLE = "Java Debug Console"; protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; private VMHandler vmHandler = new VMHandler(); @@ -76,6 +77,8 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume ((Connector.IntegerArgument) args.get("timeout")).setValue(ATTACH_TERMINAL_TIMEOUT); String address = listenConnector.startListening(args); + final String[] names = launchArguments.mainClass.split("[/\\.]"); + final String terminalName = "Debug: " + names[names.length - 1]; String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, address); RunInTerminalRequestArguments requestArgs = null; if (launchArguments.console == CONSOLE.integratedTerminal) { @@ -83,13 +86,13 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume cmds, launchArguments.cwd, launchArguments.env, - TERMINAL_TITLE); + terminalName); } else { requestArgs = RunInTerminalRequestArguments.createExternalTerminal( cmds, launchArguments.cwd, launchArguments.env, - TERMINAL_TITLE); + terminalName); } Request request = new Request(Command.RUNINTERMINAL.getName(), (JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class)); @@ -103,10 +106,24 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume if (runResponse != null) { if (runResponse.success) { try { + try { + RunInTerminalResponseBody terminalResponse = JsonUtils.fromJson( + JsonUtils.toJson(runResponse.body), RunInTerminalResponseBody.class); + context.setProcessId(terminalResponse.processId); + context.setShellProcessId(terminalResponse.shellProcessId); + } catch (JsonSyntaxException e) { + logger.severe("Failed to resolve runInTerminal response: " + e.toString()); + } VirtualMachine vm = listenConnector.accept(args); vmHandler.connectVirtualMachine(vm); context.setDebugSession(new DebugSession(vm, logger)); logger.info("Launching debuggee in terminal console succeeded."); + if (context.getShellProcessId() > 0) { + ProcessHandle debuggeeProcess = LaunchUtils.findJavaProcessInTerminalShell(context.getShellProcessId(), cmds[0], 0); + if (debuggeeProcess != null) { + context.setProcessId(debuggeeProcess.pid()); + } + } resultFuture.complete(response); } catch (TransportTimeoutException e) { int commandLength = StringUtils.length(launchArguments.cwd) + 1; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java index 55cf2a7f9..439c71cf2 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2018 Microsoft Corporation and others. +* Copyright (c) 2018-2022 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -21,6 +21,7 @@ import java.util.logging.Logger; import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -32,11 +33,11 @@ import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; import com.microsoft.java.debug.core.protocol.Requests.RunInTerminalRequestArguments; +import com.microsoft.java.debug.core.protocol.Responses.RunInTerminalResponseBody; import com.sun.jdi.connect.IllegalConnectorArgumentsException; import com.sun.jdi.connect.VMStartException; public class LaunchWithoutDebuggingDelegate extends AbstractLaunchDelegate { - protected static final String TERMINAL_TITLE = "Java Process Console"; protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; private Consumer terminateHandler; @@ -89,14 +90,16 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume final String launchInTerminalErrorFormat = "Failed to launch debuggee in terminal. Reason: %s"; + final String[] names = launchArguments.mainClass.split("[/\\.]"); + final String terminalName = "Run: " + names[names.length - 1]; String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, null); RunInTerminalRequestArguments requestArgs = null; if (launchArguments.console == CONSOLE.integratedTerminal) { requestArgs = RunInTerminalRequestArguments.createIntegratedTerminal(cmds, launchArguments.cwd, - launchArguments.env, TERMINAL_TITLE); + launchArguments.env, terminalName); } else { requestArgs = RunInTerminalRequestArguments.createExternalTerminal(cmds, launchArguments.cwd, - launchArguments.env, TERMINAL_TITLE); + launchArguments.env, terminalName); } Request request = new Request(Command.RUNINTERMINAL.getName(), (JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class)); @@ -111,9 +114,32 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume context.getProtocolServer().sendRequest(request, RUNINTERMINAL_TIMEOUT).whenComplete((runResponse, ex) -> { if (runResponse != null) { if (runResponse.success) { - // Without knowing the pid, debugger has lost control of the process. - // So simply send `terminated` event to end the session. - context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + ProcessHandle debuggeeProcess = null; + try { + RunInTerminalResponseBody terminalResponse = JsonUtils.fromJson( + JsonUtils.toJson(runResponse.body), RunInTerminalResponseBody.class); + context.setProcessId(terminalResponse.processId); + context.setShellProcessId(terminalResponse.shellProcessId); + + if (terminalResponse.processId > 0) { + debuggeeProcess = ProcessHandle.of(terminalResponse.processId).orElse(null); + } else if (terminalResponse.shellProcessId > 0) { + debuggeeProcess = LaunchUtils.findJavaProcessInTerminalShell(terminalResponse.shellProcessId, cmds[0], 3000); + } + + if (debuggeeProcess != null) { + context.setProcessId(debuggeeProcess.pid()); + debuggeeProcess.onExit().thenAcceptAsync(proc -> { + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + }); + } + } catch (JsonSyntaxException e) { + logger.severe("Failed to resolve runInTerminal response: " + e.toString()); + } + + if (debuggeeProcess == null || !debuggeeProcess.isAlive()) { + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + } resultFuture.complete(response); } else { resultFuture.completeExceptionally( diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ProcessIdHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ProcessIdHandler.java new file mode 100644 index 000000000..d3eb5ad13 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ProcessIdHandler.java @@ -0,0 +1,43 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Responses.ProcessIdResponseBody; + +public class ProcessIdHandler implements IDebugRequestHandler { + @Override + public List getTargetCommands() { + return Arrays.asList(Command.PROCESSID); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + long processId = context.getProcessId(); + long shellProcessId = context.getShellProcessId(); + if (context.getDebuggeeProcess() != null) { + processId = context.getDebuggeeProcess().pid(); + } + + response.body = new ProcessIdResponseBody(processId, shellProcessId); + return CompletableFuture.completedFuture(response); + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java index 29f4b697f..e67bddc6c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java @@ -43,6 +43,7 @@ import com.sun.jdi.BooleanValue; import com.sun.jdi.Field; import com.sun.jdi.ObjectReference; +import com.sun.jdi.StringReference; import com.sun.jdi.ReferenceType; import com.sun.jdi.ThreadReference; import com.sun.jdi.Value; @@ -224,6 +225,12 @@ public static boolean handleEvaluationResult(IDebugAdapterContext context, Threa context.getProtocolServer().sendEvent(new Events.UserNotificationEvent( Events.UserNotificationEvent.NotificationType.ERROR, String.format("[Logpoint] Log message '%s' error: %s", breakpoint.getLogMessage(), ex.getMessage()))); + } else if (value != null) { + if (value instanceof StringReference) { + String message = ((StringReference) value).value(); + context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput( + message + System.lineSeparator())); + } } return true; } else { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java new file mode 100644 index 000000000..65b09ee69 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java @@ -0,0 +1,196 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.IEvaluatableBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; +import com.microsoft.java.debug.core.MethodBreakpoint; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.BreakpointEvent; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetFunctionBreakpointsArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types.Breakpoint; +import com.microsoft.java.debug.core.protocol.Types.FunctionBreakpoint; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.MethodEntryEvent; + +public class SetFunctionBreakpointsRequestHandler implements IDebugRequestHandler { + private boolean registered = false; + + private final Logger logger; + + public SetFunctionBreakpointsRequestHandler(Logger logger) { + this.logger = logger; + } + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETFUNCTIONBREAKPOINTS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, + "Empty debug session."); + } + + if (!registered) { + registered = true; + registerMethodBreakpointHandler(context); + } + + SetFunctionBreakpointsArguments funcBpArgs = (SetFunctionBreakpointsArguments) arguments; + IMethodBreakpoint[] requestedMethodBreakpoints = (funcBpArgs.breakpoints == null) ? new IMethodBreakpoint[0] + : new MethodBreakpoint[funcBpArgs.breakpoints.length]; + for (int i = 0; i < requestedMethodBreakpoints.length; i++) { + FunctionBreakpoint funcBreakpoint = funcBpArgs.breakpoints[i]; + if (funcBreakpoint.name != null) { + String[] segments = funcBreakpoint.name.split("#"); + if (segments.length == 2 && StringUtils.isNotBlank(segments[0]) + && StringUtils.isNotBlank(segments[1])) { + int hitCount = 0; + try { + hitCount = Integer.parseInt(funcBreakpoint.hitCondition); + } catch (NumberFormatException e) { + hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition. + } + requestedMethodBreakpoints[i] = context.getDebugSession().createFunctionBreakpoint(segments[0], + segments[1], + funcBreakpoint.condition, hitCount); + } + } + } + + IMethodBreakpoint[] currentMethodBreakpoints = context.getBreakpointManager() + .setMethodBreakpoints(requestedMethodBreakpoints); + List breakpoints = new ArrayList<>(); + for (int i = 0; i < currentMethodBreakpoints.length; i++) { + if (currentMethodBreakpoints[i] == null) { + breakpoints.add(new Breakpoint(false)); + continue; + } + + // If the requested method breakpoint exists in the manager, it will reuse + // the cached breakpoint exists object. + // Otherwise add the requested method breakpoint to the cache. + // So if the returned method breakpoint from the manager is same as the + // requested method breakpoint, this means it's a new method breakpoint, need + // install it. + if (currentMethodBreakpoints[i] == requestedMethodBreakpoints[i]) { + currentMethodBreakpoints[i].install().thenAccept(wp -> { + BreakpointEvent bpEvent = new BreakpointEvent("changed", convertDebuggerMethodToClient(wp)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } else { + if (currentMethodBreakpoints[i].getHitCount() != requestedMethodBreakpoints[i].getHitCount()) { + currentMethodBreakpoints[i].setHitCount(requestedMethodBreakpoints[i].getHitCount()); + } + + if (!Objects.equals(currentMethodBreakpoints[i].getCondition(), + requestedMethodBreakpoints[i].getCondition())) { + currentMethodBreakpoints[i].setCondition(requestedMethodBreakpoints[i].getCondition()); + } + } + + breakpoints.add(convertDebuggerMethodToClient(currentMethodBreakpoints[i])); + } + + response.body = new Responses.SetDataBreakpointsResponseBody(breakpoints); + return CompletableFuture.completedFuture(response); + } + + private Breakpoint convertDebuggerMethodToClient(IMethodBreakpoint methodBreakpoint) { + return new Breakpoint((int) methodBreakpoint.getProperty("id"), + methodBreakpoint.getProperty("verified") != null && (boolean) methodBreakpoint.getProperty("verified")); + } + + private void registerMethodBreakpointHandler(IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof MethodEntryEvent) + .subscribe(debugEvent -> { + MethodEntryEvent methodEntryEvent = (MethodEntryEvent) debugEvent.event; + ThreadReference bpThread = methodEntryEvent.thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + + // Find the method breakpoint related to this method entry event + IMethodBreakpoint methodBreakpoint = Stream + .of(context.getBreakpointManager().getMethodBreakpoints()) + .filter(mp -> { + return mp.requests().contains(methodEntryEvent.request()) + && matches(methodEntryEvent, mp); + }) + .findFirst().orElse(null); + + if (methodBreakpoint != null) { + if (methodBreakpoint instanceof IEvaluatableBreakpoint + && ((IEvaluatableBreakpoint) methodBreakpoint).containsConditionalExpression()) { + if (engine.isInEvaluation(bpThread)) { + return; + } + CompletableFuture.runAsync(() -> { + engine.evaluateForBreakpoint((IEvaluatableBreakpoint) methodBreakpoint, bpThread) + .whenComplete((value, ex) -> { + boolean resume = SetBreakpointsRequestHandler.handleEvaluationResult( + context, bpThread, (IEvaluatableBreakpoint) methodBreakpoint, + value, + ex, + logger); + // Clear the evaluation environment caused by above evaluation. + engine.clearState(bpThread); + + if (resume) { + debugEvent.eventSet.resume(); + } else { + context.getProtocolServer().sendEvent(new Events.StoppedEvent( + "function breakpoint", bpThread.uniqueID())); + } + }); + }); + + } else { + context.getProtocolServer() + .sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID())); + } + + debugEvent.shouldResume = false; + } + }); + } + } + + private boolean matches(MethodEntryEvent methodEntryEvent, IMethodBreakpoint breakpoint) { + return breakpoint.className().equals(methodEntryEvent.location().declaringType().name()) + && breakpoint.methodName().equals(methodEntryEvent.method().name()); + } + +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java index 089a55b9d..021bae609 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java @@ -95,7 +95,9 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrame stackFrame String methodName = formatMethodName(method, true, true); int lineNumber = AdapterUtils.convertLineNumber(location.lineNumber(), context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); // Line number returns -1 if the information is not available; specifically, always returns -1 for native methods. + String presentationHint = null; if (lineNumber < 0) { + presentationHint = "subtle"; if (method.isNative()) { // For native method, display a tip text "native method" in the Call Stack View. methodName += "[native method]"; @@ -105,7 +107,7 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrame stackFrame clientSource = null; } } - return new Types.StackFrame(frameId, methodName, clientSource, lineNumber, context.isClientColumnsStartAt1() ? 1 : 0); + return new Types.StackFrame(frameId, methodName, clientSource, lineNumber, context.isClientColumnsStartAt1() ? 1 : 0, presentationHint); } private Types.Source convertDebuggerSourceToClient(Location location, IDebugAdapterContext context) throws URISyntaxException { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java index 94e0d3078..dc200fe38 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java @@ -15,8 +15,6 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -import org.apache.commons.lang3.ArrayUtils; - import com.microsoft.java.debug.core.DebugEvent; import com.microsoft.java.debug.core.DebugUtility; import com.microsoft.java.debug.core.IDebugSession; @@ -26,12 +24,12 @@ import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IStepFilterProvider; import com.microsoft.java.debug.core.protocol.Events; import com.microsoft.java.debug.core.protocol.Messages.Response; import com.microsoft.java.debug.core.protocol.Requests.Arguments; import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.StepArguments; -import com.microsoft.java.debug.core.protocol.Requests.StepFilters; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.Location; import com.sun.jdi.Method; @@ -145,12 +143,14 @@ public CompletableFuture handle(Command command, Arguments arguments, private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, IDebugAdapterContext context, ThreadState threadState) { Event event = debugEvent.event; + EventRequestManager eventRequestManager = debugSession.getVM().eventRequestManager(); // When a breakpoint occurs, abort any pending step requests from the same thread. if (event instanceof BreakpointEvent || event instanceof ExceptionEvent) { long threadId = ((LocatableEvent) event).thread().uniqueID(); if (threadId == threadState.threadId && threadState.pendingStepRequest != null) { - threadState.deleteStepRequests(debugSession.getVM().eventRequestManager()); + threadState.deleteStepRequest(eventRequestManager); + threadState.deleteMethodExitRequest(eventRequestManager); context.getStepResultManager().removeMethodResult(threadId); if (threadState.eventSubscription != null) { threadState.eventSubscription.dispose(); @@ -158,29 +158,29 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } } else if (event instanceof StepEvent) { ThreadReference thread = ((StepEvent) event).thread(); - threadState.deleteStepRequests(debugSession.getVM().eventRequestManager()); - if (isStepFiltersConfigured(context.getStepFilters())) { - try { - if (threadState.pendingStepType == Command.STEPIN) { - int currentStackDepth = thread.frameCount(); - Location currentStepLocation = getTopFrame(thread).location(); + threadState.deleteStepRequest(eventRequestManager); + IStepFilterProvider stepFilter = context.getProvider(IStepFilterProvider.class); + try { + if (threadState.pendingStepType == Command.STEPIN) { + int currentStackDepth = thread.frameCount(); + Location currentStepLocation = getTopFrame(thread).location(); - // If the ending step location is filtered, or same as the original location where the step into operation is originated, - // do another step of the same kind. - if (shouldFilterLocation(threadState.stepLocation, currentStepLocation, context) - || shouldDoExtraStepInto(threadState.stackDepth, threadState.stepLocation, currentStackDepth, currentStepLocation)) { - threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, - context.getStepFilters().allowClasses, - context.getStepFilters().skipClasses); - threadState.pendingStepRequest.enable(); - debugEvent.shouldResume = true; - return; - } + // If the ending step location is filtered, or same as the original location where the step into operation is originated, + // do another step of the same kind. + if (shouldFilterLocation(threadState.stepLocation, currentStepLocation, stepFilter, context) + || shouldDoExtraStepInto(threadState.stackDepth, threadState.stepLocation, currentStackDepth, currentStepLocation)) { + threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + threadState.pendingStepRequest.enable(); + debugEvent.shouldResume = true; + return; } - } catch (IncompatibleThreadStateException | IndexOutOfBoundsException ex) { - // ignore. } + } catch (IncompatibleThreadStateException | IndexOutOfBoundsException ex) { + // ignore. } + threadState.deleteMethodExitRequest(eventRequestManager); if (threadState.eventSubscription != null) { threadState.eventSubscription.dispose(); } @@ -202,33 +202,19 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } } - private boolean isStepFiltersConfigured(StepFilters filters) { - if (filters == null) { - return false; - } - return ArrayUtils.isNotEmpty(filters.allowClasses) || ArrayUtils.isNotEmpty(filters.skipClasses) - || ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors - || filters.skipStaticInitializers || filters.skipSynthetics; - } - /** * Return true if the StepEvent's location is a Method that the user has indicated to filter. * * @throws IncompatibleThreadStateException * if the thread is not suspended in the target VM. */ - private boolean shouldFilterLocation(Location originalLocation, Location currentLocation, IDebugAdapterContext context) + private boolean shouldFilterLocation(Location originalLocation, Location currentLocation, IStepFilterProvider stepFilter, IDebugAdapterContext context) throws IncompatibleThreadStateException { if (originalLocation == null || currentLocation == null) { return false; } - return !shouldFilterMethod(originalLocation.method(), context) && shouldFilterMethod(currentLocation.method(), context); - } - - private boolean shouldFilterMethod(Method method, IDebugAdapterContext context) { - return (context.getStepFilters().skipStaticInitializers && method.isStaticInitializer()) - || (context.getStepFilters().skipSynthetics && method.isSynthetic()) - || (context.getStepFilters().skipConstructors && method.isConstructor()); + return !stepFilter.skip(originalLocation.method(), context.getStepFilters()) + && stepFilter.skip(currentLocation.method(), context.getStepFilters()); } /** @@ -280,10 +266,13 @@ class ThreadState { Location stepLocation = null; Disposable eventSubscription = null; - public void deleteStepRequests(EventRequestManager manager) { - DebugUtility.deleteEventRequestSafely(manager, this.pendingStepRequest); + public void deleteMethodExitRequest(EventRequestManager manager) { DebugUtility.deleteEventRequestSafely(manager, this.pendingMethodExitRequest); this.pendingMethodExitRequest = null; + } + + public void deleteStepRequest(EventRequestManager manager) { + DebugUtility.deleteEventRequestSafely(manager, this.pendingStepRequest); this.pendingStepRequest = null; } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java index 98451bc81..b73562e7c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017-2020 Microsoft Corporation and others. +* Copyright (c) 2017-2021 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -19,9 +19,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -48,6 +46,7 @@ import com.microsoft.java.debug.core.protocol.Requests.Arguments; import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.VariablesArguments; +import com.microsoft.java.debug.core.protocol.Types.VariablePresentationHint; import com.microsoft.java.debug.core.protocol.Responses; import com.microsoft.java.debug.core.protocol.Types; import com.sun.jdi.AbsentInformationException; @@ -99,6 +98,15 @@ public CompletableFuture handle(Command command, Arguments arguments, } VariableProxy containerNode = (VariableProxy) container; + + if (containerNode.isLazyVariable() && DebugSettings.getCurrent().showToString) { + Types.Variable typedVariable = this.resolveLazyVariable(context, containerNode, variableFormatter, options, evaluationEngine); + if (typedVariable != null) { + list.add(typedVariable); + response.body = new Responses.VariablesResponseBody(list); + return CompletableFuture.completedFuture(response); + } + } List childrenList = new ArrayList<>(); IStackFrameManager stackFrameManager = context.getStackFrameManager(); String containerEvaluateName = containerNode.getEvaluateName(); @@ -136,7 +144,12 @@ public CompletableFuture handle(Command command, Arguments arguments, try { ObjectReference containerObj = (ObjectReference) containerNode.getProxiedVariable(); if (DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) { - JavaLogicalStructure logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj); + JavaLogicalStructure logicalStructure = null; + try { + logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to get the logical structure for the variable, fall back to the Object view.", e); + } if (isUnboundedTypeContainer && logicalStructure != null && containerEvaluateName != null) { containerEvaluateName = "((" + logicalStructure.getFullyQualifiedName() + ")" + containerEvaluateName + ")"; isUnboundedTypeContainer = false; @@ -165,11 +178,8 @@ public CompletableFuture handle(Command command, Arguments arguments, childrenList.add(variable); } } - } catch (IllegalArgumentException | CancellationException | InterruptedException | ExecutionException e) { - logger.log(Level.WARNING, - String.format("Failed to get the logical structure for the type %s, fall back to the Object view.", - containerObj.type().name()), - e); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to get the logical structure for the variable, fall back to the Object view.", e); } logicalStructure = null; @@ -244,9 +254,8 @@ public CompletableFuture handle(Command command, Arguments arguments, indexedVariables = ((IntegerValue) sizeValue).value(); } } - } catch (CancellationException | IllegalArgumentException | InterruptedException | ExecutionException | UnsupportedOperationException e) { - logger.log(Level.INFO, - String.format("Failed to get the logical size for the type %s.", value.type().name()), e); + } catch (Exception e) { + logger.log(Level.INFO, "Failed to get the logical size of the variable", e); } } @@ -270,23 +279,65 @@ public CompletableFuture handle(Command command, Arguments arguments, } } - int referenceId = 0; + VariableProxy varProxy = null; if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) { - VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName); - referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy); + varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName); varProxy.setIndexedVariable(indexedVariables >= 0); varProxy.setUnboundedType(javaVariable.isUnboundedType()); } - Types.Variable typedVariables = new Types.Variable(name, variableFormatter.valueToString(value, options), - variableFormatter.typeToString(value == null ? null : value.type(), options), - referenceId, evaluateName); - typedVariables.indexedVariables = Math.max(indexedVariables, 0); + boolean hasErrors = false; + String valueString = null; + try { + valueString = variableFormatter.valueToString(value, options); + } catch (OutOfMemoryError e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to convert the value of a large object to a string", e); + valueString = ""; + } catch (Exception e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to resolve the variable value", e); + valueString = ""; + } + + String typeString = ""; + try { + typeString = variableFormatter.typeToString(value == null ? null : value.type(), options); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to resolve the variable type", e); + typeString = ""; + } + String detailsValue = null; - if (sizeValue != null) { + if (hasErrors) { + // If failed to resolve the variable value, skip the details info as well. + } else if (sizeValue != null) { detailsValue = "size=" + variableFormatter.valueToString(sizeValue, options); } else if (DebugSettings.getCurrent().showToString) { - detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine); + if (VariableDetailUtils.isLazyLoadingSupported(value) && varProxy != null) { + varProxy.setLazyVariable(true); + } else { + try { + detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine); + } catch (OutOfMemoryError e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e); + detailsValue = ""; + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value", e); + detailsValue = ""; + } + } + } + + int referenceId = 0; + if (varProxy != null) { + referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy); + } + + Types.Variable typedVariables = new Types.Variable(name, valueString, typeString, referenceId, evaluateName); + typedVariables.indexedVariables = Math.max(indexedVariables, 0); + if (varProxy != null && varProxy.isLazyVariable()) { + typedVariables.presentationHint = new VariablePresentationHint(true); } if (detailsValue != null) { @@ -304,6 +355,25 @@ public CompletableFuture handle(Command command, Arguments arguments, return CompletableFuture.completedFuture(response); } + private Types.Variable resolveLazyVariable(IDebugAdapterContext context, VariableProxy containerNode, IVariableFormatter variableFormatter, + Map options, IEvaluationProvider evaluationEngine) { + VariableProxy valueReferenceProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), + containerNode.getProxiedVariable(), null /** container */, containerNode.getEvaluateName()); + valueReferenceProxy.setIndexedVariable(containerNode.isIndexedVariable()); + valueReferenceProxy.setUnboundedType(containerNode.isUnboundedType()); + int referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), valueReferenceProxy); + // this proxiedVariable is intermediate object, see https://github.com/microsoft/vscode/issues/135147#issuecomment-1076240074 + Object proxiedVariable = containerNode.getProxiedVariable(); + if (proxiedVariable instanceof ObjectReference) { + ObjectReference variable = (ObjectReference) proxiedVariable; + String valueString = variableFormatter.valueToString(variable, options); + String detailString = VariableDetailUtils.formatDetailsValue(variable, containerNode.getThread(), variableFormatter, options, + evaluationEngine); + return new Types.Variable("", valueString + " " + detailString, "", referenceId, containerNode.getEvaluateName()); + } + return null; + } + private Set getDuplicateNames(Collection list) { Set result = new HashSet<>(); Set set = new HashSet<>(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java index e82ab79f9..c6d0d38e0 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java @@ -154,4 +154,18 @@ private static boolean isClassType(Value value, String typeName) { return Objects.equals(((ObjectReference) value).type().name(), typeName); } + + public static boolean isLazyLoadingSupported(Value value) { + if (isClassType(value, STRING_TYPE)) { + return false; + } + if (!(value instanceof ObjectReference)) { + return false; + } + String inheritedType = findInheritedType(value, COLLECTION_TYPES); + if (inheritedType == null && !containsToStringMethod((ObjectReference) value)) { + return false; + } + return true; + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java index 9f208464c..5ebf93735 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java @@ -24,6 +24,7 @@ public class VariableProxy { private final String evaluateName; private boolean isIndexedVariable; private boolean isUnboundedType = false; + private boolean isLazyVariable = false; /** * Create a variable reference. @@ -75,7 +76,8 @@ public boolean equals(Object obj) { } VariableProxy other = (VariableProxy) obj; return Objects.equals(scopeName, other.scopeName) && Objects.equals(getThreadId(), other.getThreadId()) - && Objects.equals(variable, other.variable) && Objects.equals(evaluateName, other.evaluateName); + && Objects.equals(variable, other.variable) && Objects.equals(evaluateName, other.evaluateName) + && Objects.equals(isLazyVariable, other.isLazyVariable); } public long getThreadId() { @@ -109,4 +111,13 @@ public boolean isUnboundedType() { public void setUnboundedType(boolean isUnboundedType) { this.isUnboundedType = isUnboundedType; } + + public boolean isLazyVariable() { + return isLazyVariable; + } + + public void setLazyVariable(boolean isLazyVariable) { + this.isLazyVariable = isLazyVariable; + } + } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java index 5397c418e..681ec543d 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java @@ -283,4 +283,26 @@ public InvalidatedEvent(InvalidatedAreas area, int frameId) { this.frameId = frameId; } } + + public static class ProcessIdNotification extends DebugEvent { + /** + * The process ID. + */ + public long processId = -1; + /** + * The process ID of the terminal shell if the process is running in a terminal shell. + */ + public long shellProcessId = -1; + + public ProcessIdNotification(long processId) { + super("processid"); + this.processId = processId; + } + + public ProcessIdNotification(long processId, long shellProcessId) { + super("processid"); + this.processId = processId; + this.shellProcessId = shellProcessId; + } + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java index e31a65261..9b444155c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java @@ -410,6 +410,7 @@ public static enum Command { PAUSEOTHERS("pauseOthers", ThreadOperationArguments.class), INLINEVALUES("inlineValues", InlineValuesArguments.class), REFRESHVARIABLES("refreshVariables", RefreshVariablesArguments.class), + PROCESSID("processId", Arguments.class), UNSUPPORTED("", Arguments.class); private String command; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java index cc349b6e7..2210c8e93 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java @@ -39,11 +39,34 @@ public InitializeResponseBody(Types.Capabilities capabilities) { } } - public static class RunInTerminalResponseBody extends ResponseBody { - public int processId; + public static class ProcessIdResponseBody extends ResponseBody { + /** + * The process ID. + */ + public long processId = -1; + /** + * The process ID of the terminal shell if the process is running in a terminal shell. + */ + public long shellProcessId = -1; + + public ProcessIdResponseBody(long processId) { + this.processId = processId; + } - public RunInTerminalResponseBody(int processId) { + public ProcessIdResponseBody(long processId, long shellProcessId) { this.processId = processId; + this.shellProcessId = shellProcessId; + } + } + + public static class RunInTerminalResponseBody extends ProcessIdResponseBody { + + public RunInTerminalResponseBody(long processId) { + super(processId); + } + + public RunInTerminalResponseBody(long processId, long shellProcessId) { + super(processId, shellProcessId); } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java index 0b9a0494a..cd20faf4f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java @@ -43,6 +43,8 @@ public static class StackFrame { public int line; public int column; public String name; + public String presentationHint; + /** * Constructs a StackFrame with the given information. @@ -57,13 +59,17 @@ public static class StackFrame { * line number of the stack frame * @param col * column number of the stack frame + * @param presentationHint + * An optional hint for how to present this frame in the UI. + * Values: 'normal', 'label', 'subtle' */ - public StackFrame(int id, String name, Source src, int ln, int col) { + public StackFrame(int id, String name, Source src, int ln, int col, String presentationHint) { this.id = id; this.name = name; this.source = src; this.line = ln; this.column = col; + this.presentationHint = presentationHint; } } @@ -90,6 +96,7 @@ public static class Variable { public int namedVariables; public int indexedVariables; public String evaluateName; + public VariablePresentationHint presentationHint; /** * Constructor. @@ -343,6 +350,14 @@ public static class ExceptionDetails { public ExceptionDetails[] innerException; } + public static class VariablePresentationHint { + public boolean lazy; + + public VariablePresentationHint(boolean lazy) { + this.lazy = lazy; + } + } + public static class Capabilities { public boolean supportsConfigurationDoneRequest; public boolean supportsHitConditionalBreakpoints; @@ -359,5 +374,6 @@ public static class Capabilities { public ExceptionBreakpointFilter[] exceptionBreakpointFilters = new ExceptionBreakpointFilter[0]; public boolean supportsDataBreakpoints; public boolean supportsClipboardContext; + public boolean supportsFunctionBreakpoints; } } diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath index 8c76f8dd3..94fa67e10 100644 --- a/com.microsoft.java.debug.plugin/.classpath +++ b/com.microsoft.java.debug.plugin/.classpath @@ -1,11 +1,16 @@ - + + + + + + - + - - - + + + diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF index 2245a3854..8f4badd62 100644 --- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF +++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF @@ -2,8 +2,8 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Java Debug Server Plugin Bundle-SymbolicName: com.microsoft.java.debug.plugin;singleton:=true -Bundle-Version: 0.32.0 -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-Version: 0.38.0 +Bundle-RequiredExecutionEnvironment: JavaSE-11 Bundle-ActivationPolicy: lazy Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin Bundle-Vendor: Microsoft @@ -21,8 +21,8 @@ Require-Bundle: org.eclipse.core.runtime, org.apache.commons.lang3, org.eclipse.lsp4j, com.google.guava -Bundle-ClassPath: lib/commons-io-2.5.jar, +Bundle-ClassPath: lib/commons-io-2.11.0.jar, ., - lib/rxjava-2.1.1.jar, - lib/reactive-streams-1.0.0.jar, - lib/com.microsoft.java.debug.core-0.32.0.jar + lib/rxjava-2.2.21.jar, + lib/reactive-streams-1.0.4.jar, + lib/com.microsoft.java.debug.core-0.38.0+1.jar diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml index f5e0003e3..e041dc7d9 100644 --- a/com.microsoft.java.debug.plugin/pom.xml +++ b/com.microsoft.java.debug.plugin/pom.xml @@ -5,7 +5,7 @@ com.microsoft.java java-debug-parent - 0.32.0 + 0.38.0 com.microsoft.java.debug.plugin eclipse-plugin @@ -17,6 +17,17 @@ tycho-maven-plugin ${tycho-version} true + + + org.eclipse.tycho + tycho-compiler-plugin + ${tycho-version} + + + --limit-modules + java.base,java.logging,java.xml,jdk.jdi,java.compiler + + org.apache.maven.plugins @@ -30,22 +41,22 @@ io.reactivex.rxjava2 rxjava - 2.1.1 + 2.2.21 org.reactivestreams reactive-streams - 1.0.0 + 1.0.4 commons-io commons-io - 2.5 + 2.11.0 - com.microsoft.java + ch.epfl.scala com.microsoft.java.debug.core - 0.32.0 + 0.38.0+1 diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java index 607d26a4e..3551ab2e6 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java @@ -13,16 +13,22 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.apache.commons.io.IOUtils; import org.eclipse.jdi.internal.VirtualMachineImpl; import org.eclipse.jdi.internal.VirtualMachineManagerImpl; import org.eclipse.jdi.internal.connect.SocketLaunchingConnectorImpl; import org.eclipse.jdi.internal.connect.SocketListeningConnectorImpl; import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.LaunchException; import com.sun.jdi.VirtualMachine; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.IllegalConnectorArgumentsException; @@ -83,18 +89,100 @@ public VirtualMachine launch(Map connectionArgs) String address = listenConnector.startListening(args); String[] cmds = constructLaunchCommand(connectionArgs, address); - Process process = Runtime.getRuntime().exec(cmds, envVars, workingDir); - VirtualMachineImpl vm; + /* Launch the Java process */ + final Process process = Runtime.getRuntime().exec(cmds, envVars, workingDir); + + /* A Future that will be completed if we successfully connect to the launched process, or + will fail with an Exception if we do not. + */ + final CompletableFuture result = new CompletableFuture<>(); + + /* Listen for the debug connection from the Java process */ + CompletableFuture.runAsync(() -> { + try { + VirtualMachineImpl vm = (VirtualMachineImpl) listenConnector.accept(args); + vm.setLaunchedProcess(process); + result.complete(vm); + } catch (IllegalConnectorArgumentsException e) { + result.completeExceptionally(e); + } catch (IOException e) { + if (result.isDone()) { + /* The result Future has already been completed by the Process onExit hook */ + return; + } + + final String stdout = streamToString(process.getInputStream()); + final String stderr = streamToString(process.getErrorStream()); + + process.destroy(); + + result.completeExceptionally(new LaunchException( + String.format("VM did not connect within given time: %d ms", ACCEPT_TIMEOUT), + process, + false, + -1, + stdout, + stderr + )); + } catch (RuntimeException e) { + result.completeExceptionally(e); + } + }); + + /* Wait for the Java process to exit; if it exits before the debug connection is made, report it as an error. */ + process.onExit().thenAcceptAsync(theProcess -> { + if (result.isDone()) { + /* The result Future has already been completed by successfully connecting to the debug connection */ + return; + } + + final int exitStatus = theProcess.exitValue(); + final String stdout = streamToString(process.getInputStream()); + final String stderr = streamToString(process.getErrorStream()); + + result.completeExceptionally(new LaunchException( + String.format("VM exited with status %d", exitStatus), + theProcess, + true, + exitStatus, + stdout, + stderr + )); + + /* Stop the debug connection attempt */ + try { + listenConnector.stopListening(args); + } catch (IOException e) { + /* Ignore */ + } + }); + try { - vm = (VirtualMachineImpl) listenConnector.accept(args); - } catch (IOException | IllegalConnectorArgumentsException e) { - process.destroy(); - throw new VMStartException(String.format("VM did not connect within given time: %d ms", ACCEPT_TIMEOUT), process); + return result.get(); + } catch (ExecutionException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else if (e.getCause() instanceof IllegalConnectorArgumentsException) { + throw (IllegalConnectorArgumentsException) e.getCause(); + } else if (e.getCause() instanceof VMStartException) { + throw (VMStartException) e.getCause(); + } else if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException) e.getCause(); + } else { + throw new IllegalStateException("Unexpected exception thrown when launching VM", e.getCause()); + } + } catch (InterruptedException e) { + throw new VMStartException("VM start interrupted", process); } + } - vm.setLaunchedProcess(process); - return vm; + private String streamToString(final InputStream inputStream) { + try { + return IOUtils.toString(inputStream, StandardCharsets.UTF_8); + } catch (IOException ioe) { + return null; + } } private static String[] constructLaunchCommand(Map launchingOptions, String address) { diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtProviderContextFactory.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtProviderContextFactory.java index 7213f8e9a..37ebc2dae 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtProviderContextFactory.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtProviderContextFactory.java @@ -16,8 +16,10 @@ import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; import com.microsoft.java.debug.core.adapter.IProviderContext; import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.IStepFilterProvider; import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider; import com.microsoft.java.debug.core.adapter.ProviderContext; +import com.microsoft.java.debug.core.adapter.StepFilterProvider; import com.microsoft.java.debug.plugin.internal.eval.JdtEvaluationProvider; /** @@ -35,6 +37,7 @@ public static IProviderContext createProviderContext() { context.registerProvider(IHotCodeReplaceProvider.class, new JavaHotCodeReplaceProvider()); context.registerProvider(IEvaluationProvider.class, new JdtEvaluationProvider()); context.registerProvider(ICompletionsProvider.class, new CompletionsProvider()); + context.registerProvider(IStepFilterProvider.class, new StepFilterProvider()); return context; } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java index d24d2abdd..efc7ce20c 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2020 Microsoft Corporation and others. + * Copyright (c) 2017-2021 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -17,7 +17,6 @@ import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; @@ -120,11 +119,7 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th String filePath = AdapterUtils.toPath(uri); // For file uri, read the file contents directly and pass them to the ast parser. if (filePath != null && Files.isRegularFile(Paths.get(filePath))) { - Charset cs = (Charset) this.options.get(Constants.DEBUGGEE_ENCODING); - if (cs == null) { - cs = Charset.defaultCharset(); - } - String source = readFile(filePath, cs); + String source = readFile(filePath); parser.setSource(source.toCharArray()); /** * See the java doc for { @link ASTParser#setResolveBindings(boolean) }. @@ -293,10 +288,10 @@ private static IClassFile resolveClassFile(String uriString) { return null; } - private static String readFile(String filePath, Charset cs) { + private static String readFile(String filePath) { StringBuilder builder = new StringBuilder(); try (BufferedReader bufferReader = - new BufferedReader(new InputStreamReader(new FileInputStream(filePath), cs))) { + new BufferedReader(new InputStreamReader(new FileInputStream(filePath)))) { final int BUFFER_SIZE = 4096; char[] buffer = new char[BUFFER_SIZE]; while (true) { diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java index 9d3b1014e..f70d0c044 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Microsoft Corporation and others. + * Copyright (c) 2017-2022 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -12,6 +12,7 @@ package com.microsoft.java.debug.plugin.internal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -21,6 +22,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.SourceVersion; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IFile; @@ -47,8 +51,10 @@ public class ResolveMainClassHandler { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - // Java command line supports two kinds of main class format: and [/] - private static final String CLASSNAME_REGX = "([$\\w]+\\.)*[$\\w]+(/([$\\w]+\\.)*[$\\w]+)?"; + private static final int CONFIGERROR_INVALID_CLASS_NAME = 1; + private static final int CONFIGERROR_MAIN_CLASS_NOT_EXIST = 2; + private static final int CONFIGERROR_MAIN_CLASS_NOT_UNIQUE = 3; + private static final int CONFIGERROR_INVALID_JAVA_PROJECT = 4; /** * resolve main class and project name. @@ -75,15 +81,26 @@ public Object validateLaunchConfig(List arguments) throws Exception { } private List resolveMainClassCore(List arguments) { - IPath rootPath = null; if (arguments != null && arguments.size() > 0 && arguments.get(0) != null) { - rootPath = ResourceUtils.filePathFromURI((String) arguments.get(0)); - } - final ArrayList targetProjectPath = new ArrayList<>(); - if (rootPath != null) { - targetProjectPath.add(rootPath); + String argument = (String) arguments.get(0); + IProject[] projects = ProjectUtils.getAllProjects(); + if (Stream.of(projects).anyMatch(project -> Objects.equals(project.getName(), argument))) { + return resolveMainClassUnderProject(argument); + } + + IPath rootPath = ResourceUtils.filePathFromURI(argument); + if (rootPath != null) { + return resolveMainClassUnderPaths(Arrays.asList(rootPath)); + } } - IJavaSearchScope searchScope = SearchEngine.createWorkspaceScope(); + + return resolveMainClassUnderPaths(Collections.emptyList()); + } + + private List resolveMainClassUnderPaths(List parentPaths) { + // Limit to search main method from source code only. + IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(ProjectUtils.getJavaProjects(), + IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES); SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH); final List res = new ArrayList<>(); @@ -108,10 +125,9 @@ public void acceptSearchMatch(SearchMatch match) { } } String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName(); - if (projectName == null - || targetProjectPath.isEmpty() - || ResourceUtils.isContainedIn(project.getLocation(), targetProjectPath) - || isContainedInInvisibleProject(project, targetProjectPath)) { + if (parentPaths.isEmpty() + || ResourceUtils.isContainedIn(project.getLocation(), parentPaths) + || isContainedInInvisibleProject(project, parentPaths)) { String filePath = null; if (match.getResource() instanceof IFile) { @@ -145,6 +161,66 @@ public void acceptSearchMatch(SearchMatch match) { return resolutions; } + private List resolveMainClassUnderProject(final String projectName) { + // Limit to search main method from source code only. + IJavaProject javaProject = ProjectUtils.getJavaProject(projectName); + IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(javaProject == null ? new IJavaProject[0] : new IJavaProject[] {javaProject}, + IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES); + SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD, + IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH); + final List res = new ArrayList<>(); + SearchRequestor requestor = new SearchRequestor() { + @Override + public void acceptSearchMatch(SearchMatch match) { + Object element = match.getElement(); + if (element instanceof IMethod) { + IMethod method = (IMethod) element; + try { + if (method.isMainMethod()) { + IResource resource = method.getResource(); + if (resource != null) { + IProject project = resource.getProject(); + if (project != null) { + String mainClass = method.getDeclaringType().getFullyQualifiedName(); + IJavaProject javaProject = JdtUtils.getJavaProject(project); + if (javaProject != null) { + String moduleName = JdtUtils.getModuleName(javaProject); + if (moduleName != null) { + mainClass = moduleName + "/" + mainClass; + } + } + + String filePath = null; + if (match.getResource() instanceof IFile) { + try { + filePath = match.getResource().getLocation().toOSString(); + } catch (Exception ex) { + // ignore + } + } + res.add(new ResolutionItem(mainClass, projectName, filePath)); + } + } + } + } catch (JavaModelException e) { + // ignore + } + } + } + }; + SearchEngine searchEngine = new SearchEngine(); + try { + searchEngine.search(pattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, + searchScope, requestor, null /* progress monitor */); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Searching the main class failure: %s", e.toString()), e); + } + + List resolutions = res.stream().distinct().collect(Collectors.toList()); + Collections.sort(resolutions); + return resolutions; + } + private boolean isContainedInInvisibleProject(IProject project, Collection rootPaths) { if (project == null) { return false; @@ -185,23 +261,41 @@ private ValidationResponse validateLaunchConfigCore(List arguments) thro private ValidationResult validateMainClass(final String mainClass, final String projectName, boolean containsExternalClasspaths) throws CoreException { if (StringUtils.isEmpty(mainClass)) { return new ValidationResult(true); - } else if (!mainClass.matches(CLASSNAME_REGX)) { - return new ValidationResult(false, String.format("ConfigError: '%s' is not a valid class name.", mainClass)); + } else if (!isValidMainClassName(mainClass)) { + return new ValidationResult(false, String.format("ConfigError: '%s' is not a valid class name.", mainClass), + CONFIGERROR_INVALID_CLASS_NAME); } if (!containsExternalClasspaths && StringUtils.isEmpty(projectName)) { List javaProjects = searchClassInProjectClasspaths(mainClass); if (javaProjects.size() == 0) { - return new ValidationResult(false, String.format("ConfigError: Main class '%s' doesn't exist in the workspace.", mainClass)); + return new ValidationResult(false, String.format("ConfigError: Main class '%s' doesn't exist in the workspace.", mainClass), + CONFIGERROR_MAIN_CLASS_NOT_EXIST); } if (javaProjects.size() > 1) { - return new ValidationResult(false, String.format("ConfigError: Main class '%s' isn't unique in the workspace.", mainClass)); + return new ValidationResult(false, String.format("ConfigError: Main class '%s' isn't unique in the workspace.", mainClass), + CONFIGERROR_MAIN_CLASS_NOT_UNIQUE); } } return new ValidationResult(true); } + // Java command line supports two kinds of main class format: and [/] + private boolean isValidMainClassName(String mainClass) { + if (StringUtils.isEmpty(mainClass)) { + return true; + } + + int index = mainClass.indexOf('/'); + if (index == -1) { + return SourceVersion.isName(mainClass); + } + + return SourceVersion.isName(mainClass.substring(0, index)) + && SourceVersion.isName(mainClass.substring(index + 1)); + } + private List searchClassInProjectClasspaths(String fullyQualifiedClassName) throws CoreException { return ResolveClasspathsHandler.getJavaProjectFromType(fullyQualifiedClassName); } @@ -212,7 +306,8 @@ private ValidationResult validateProjectName(final String mainClass, final Strin } if (JdtUtils.getJavaProject(projectName) == null) { - return new ValidationResult(false, String.format("ConfigError: The project '%s' is not a valid java project.", projectName)); + return new ValidationResult(false, String.format("ConfigError: The project '%s' is not a valid java project.", projectName), + CONFIGERROR_INVALID_JAVA_PROJECT); } return new ValidationResult(true); @@ -302,14 +397,16 @@ class ValidationResponse { class ValidationResult { boolean isValid; String message; + int kind; ValidationResult(boolean isValid) { this.isValid = isValid; } - ValidationResult(boolean isValid, String message) { + ValidationResult(boolean isValid, String message, int kind) { this.isValid = isValid; this.message = message; + this.kind = kind; } } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java index 85ff58978..a3c61e814 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018-2020 Microsoft Corporation and others. + * Copyright (c) 2018-2021 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -26,6 +26,7 @@ import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IOpenable; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; @@ -167,7 +168,7 @@ private static MainMethod extractMainMethodInfo(ICompilationUnit typeRoot, IMeth private static Range getRange(ICompilationUnit typeRoot, IJavaElement element) throws JavaModelException { ISourceRange r = ((ISourceReference) element).getNameRange(); - return JDTUtils.toRange(typeRoot, r.getOffset(), r.getLength()); + return JDTUtils.toRange((IOpenable) typeRoot, r.getOffset(), r.getLength()); } static class MainMethod { diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java index 21e2dff6e..e014552a8 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java @@ -226,9 +226,9 @@ private String logMessageToExpression(String logMessage) { } if (arguments.size() > 0) { - return "System.out.println(String.format(\"" + format + "\"," + String.join(",", arguments) + "))"; + return "String.format(\"" + format + "\"," + String.join(",", arguments) + ")"; } else { - return "System.out.println(\"" + format + "\")"; + return "\"" + format + "\""; } } diff --git a/com.microsoft.java.debug.repository/category.xml b/com.microsoft.java.debug.repository/category.xml index 88e7c7041..2b3229b2d 100644 --- a/com.microsoft.java.debug.repository/category.xml +++ b/com.microsoft.java.debug.repository/category.xml @@ -1,6 +1,6 @@ - + diff --git a/com.microsoft.java.debug.repository/pom.xml b/com.microsoft.java.debug.repository/pom.xml index 6e9099962..5e3c1ed1d 100644 --- a/com.microsoft.java.debug.repository/pom.xml +++ b/com.microsoft.java.debug.repository/pom.xml @@ -4,7 +4,7 @@ com.microsoft.java java-debug-parent - 0.32.0 + 0.38.0 com.microsoft.java.debug.repository eclipse-repository diff --git a/java.debug.target b/java.debug.target index 835c63801..dcb0b8c5e 100644 --- a/java.debug.target +++ b/java.debug.target @@ -6,17 +6,26 @@ - + + + + + + + + + + - + - + diff --git a/pom.xml b/pom.xml index 4f7abb14c..0e0c0ffee 100644 --- a/pom.xml +++ b/pom.xml @@ -6,12 +6,12 @@ ${base.name} :: Parent The Java Debug Server is an implementation of Visual Studio Code (VSCode) Debug Protocol. It can be used in Visual Studio Code to debug Java programs. https://github.com/Microsoft/java-debug - 0.32.0 + 0.38.0 pom Java Debug Server for Visual Studio Code UTF-8 - 1.5.1 + 2.7.3 ${basedir} @@ -154,9 +154,9 @@ - 201912 + 202112 p2 - https://download.eclipse.org/releases/2019-12/ + https://download.eclipse.org/releases/2021-12/202112081000/ oss.sonatype.org @@ -173,7 +173,7 @@ JBOLL.TOOLS p2 - https://download.jboss.org/jbosstools/updates/m2e-extensions/m2e-apt/1.5.0-2018-05-16_00-46-30-H11 + https://download.jboss.org/jbosstools/updates/m2e-extensions/m2e-apt/1.5.3-2019-11-08_11-04-22-H22/ orbit