8241214: Test debugging of hidden classes using jdb

Add test and enable jdb support for hidden classes

Reviewed-by: cjplummer, amenkov, mchung, lmesnik
This commit is contained in:
Serguei Spitsyn 2020-04-23 07:46:18 +00:00
parent 8d388381ee
commit e507405f5c
3 changed files with 530 additions and 1 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -101,6 +101,28 @@ class PatternReferenceTypeSpec implements ReferenceTypeSpec {
}
private void checkClassName(String className) throws ClassNotFoundException {
int slashIdx = className.indexOf("/");
// Slash is present in hidden class names only. It looks like p.Foo/0x1234.
if (slashIdx != -1) {
// A hidden class name is ending with a slash following by a suffix.
int lastSlashIdx = className.lastIndexOf("/");
int lastDotIdx = className.lastIndexOf(".");
// There must be just one slash with a following suffix but no dots.
if (slashIdx != lastSlashIdx || lastDotIdx > slashIdx || slashIdx + 1 == className.length()) {
throw new ClassNotFoundException();
}
// Check prefix and suffix separately.
String[] parts = className.split("/");
assert parts.length == 2;
className = parts[0];
String hcSuffix = parts[1];
if (!isUnqualifiedName(hcSuffix)) {
throw new ClassNotFoundException();
}
}
// Do stricter checking of class name validity on deferred
// because if the name is invalid, it will
// never match a future loaded class, and we'll be silent
@ -118,6 +140,14 @@ class PatternReferenceTypeSpec implements ReferenceTypeSpec {
}
}
private boolean isUnqualifiedName(String s) {
if (s.length() == 0) {
return false;
}
// unqualified names should have no characters: ".;/["
return !s.matches("[.;/\091]*"); // \091 is '['
}
private boolean isJavaIdentifier(String s) {
if (s.length() == 0) {
return false;

View File

@ -0,0 +1,357 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary JDB test for hidden classes
*
* @library /vmTestbase
* /test/lib
* @modules jdk.jdi
* jdk.jdwp.agent
* @run driver jdk.test.lib.FileInstaller . .
* @build nsk.jdb.hidden_class.hc001.hc001
* nsk.jdb.hidden_class.hc001.hc001a
*
* @run main/othervm PropertyResolvingWrapper nsk.jdb.hidden_class.hc001.hc001
* -arch=${os.family}-${os.simpleArch}
* -waittime=5
* -debugee.vmkind=java
* -transport.address=dynamic
* -jdb=${test.jdk}/bin/jdb
* -java.options="${test.vm.opts} ${test.java.opts}"
* -workdir=.
* -debugee.vmkeys="${test.vm.opts} ${test.java.opts}"
*/
package nsk.jdb.hidden_class.hc001;
import java.io.*;
import java.util.*;
import nsk.share.*;
import nsk.share.jdb.*;
public class hc001 extends JdbTest {
static final String DEBUGGEE_CLASS = hc001a.class.getTypeName();
static final String HC_NAME_FIELD = DEBUGGEE_CLASS + ".hcName";
static final String MAIN_METHOD_NAME = DEBUGGEE_CLASS + ".main";
static final String EMPTY_METHOD_NAME = DEBUGGEE_CLASS + ".emptyMethod";
static final String HC_METHOD_NAME = "hcMethod";
static final String HC_FIELD_NAME = "hcField";
static final int MAX_SLEEP_CNT = 3;
public static void main(String argv[]) {
System.exit(run(argv, System.out) + JCK_STATUS_BASE);
}
public static int run(String argv[], PrintStream out) {
debuggeeClass = DEBUGGEE_CLASS; // needed for JdbTest.runTest
firstBreak = MAIN_METHOD_NAME; // needed for JdbTest.runTest
return new hc001().runTest(argv, out);
}
static boolean checkPattern(String[] arr, String pattern) {
for (int idx = 0; idx < arr.length; idx++) {
String str = arr[idx];
if (str.indexOf(pattern) != -1) {
return true;
}
}
return false;
}
static void throwFailure(String msg) throws Failure {
throw new Failure(msg);
}
/* Make a required cooperated setup with the debuggee:
* - transition the debuggee's execution to expected execution point
* (emptyMethod start) at which hidden class has been already loaded
* - get the hidden class name from the debuggee
* Return the hidden class name.
*/
private String runPrologue() {
String[] reply = null;
log.println("\n### Debugger: runPrologue");
// uncomment this line to enable verbose output from jdb
// log.enableVerbose(true);
// run jdb command "stop in"
jdb.setBreakpointInMethod(EMPTY_METHOD_NAME);
log.println("\nDebugger: breakpoint is set at:\n\t" + EMPTY_METHOD_NAME);
// run jdb command "cont"
reply = jdb.receiveReplyFor(JdbCommand.cont);
if (!jdb.isAtBreakpoint(reply, EMPTY_METHOD_NAME)) {
throwFailure("Debugger: Missed breakpoint at:\n\t" + EMPTY_METHOD_NAME);
}
log.println("\nDebugger: breakpoint is hit at:\n\t" + EMPTY_METHOD_NAME);
// run jdb command "eval" for hidden class field HC_NAME_FIELD
reply = jdb.receiveReplyFor(JdbCommand.eval + HC_NAME_FIELD);
int beg = reply[0].indexOf('"') + 1;
int end = reply[0].lastIndexOf('"');
if (end == -1 || beg > end) {
log.println("\nDebugger: the jdb command:\n\t" + JdbCommand.eval + HC_NAME_FIELD);
log.println("\treturned bad reply:\n\t" + reply[0]);
throwFailure("Debugger: failed to evaluate debuggee field:\n\t" + HC_NAME_FIELD);
}
String hiddenClassName = reply[0].substring(beg, end); // we know the hidden class name now
log.println("\nDebugger: jdb command eval returned hidden class name:\n\t" + hiddenClassName);
return hiddenClassName;
}
/* Test jdb commands "classes" and "class" for hidden class. */
private void testClassCommands(String hcName) {
String[] reply = null;
log.println("\n### Debugger: testClassCommands");
// run jdb command "classes"
reply = jdb.receiveReplyFor(JdbCommand.classes);
if (!checkPattern(reply, hcName)) {
throwFailure("Debugger: expected jdb command classes to list hidden class:\n\t" + hcName);
}
log.println("\nDebugger: found matched class in jdb command classes reply:\n\t" + hcName);
// run jdb command "class" for hidden class
reply = jdb.receiveReplyFor(JdbCommand._class + hcName);
if (!checkPattern(reply, hcName)) {
throwFailure("Debugger: expected hiddenclass name in jdb command class reply: " + hcName);
}
log.println("\nDebugger: found matched class in jdb command class reply:\n\t" + hcName);
}
/* Transition the debuggee's execution to the hidden class method start. */
private void stopInHiddenClassMethod(String hcName) {
String hcMethodName = hcName + "." + HC_METHOD_NAME;
String[] reply = null;
log.println("\n### Debugger: stopInHiddenClassMethod");
// set a breakpoint in hidden class method hcMethodName()
jdb.setBreakpointInMethod(hcMethodName);
log.println("\nDebugger: breakpoint is set at:\n\t" + hcMethodName);
// run jdb command "clear": should list breakpoint in hcMethodName
reply = jdb.receiveReplyFor(JdbCommand.clear);
if (!checkPattern(reply, hcMethodName)) {
throwFailure("Debugger: expected jdb clear command to list breakpoint: " + hcMethodName);
}
log.println("\nDebugger: jdb command clear lists breakpoint at:\n\t" + hcMethodName);
// run jdb command "cont"
jdb.receiveReplyFor(JdbCommand.cont);
log.println("\nDebugger: executed jdb command cont");
}
/* Test the jdb commands "up" and "where" for hidden class. */
private void testUpWhereCommands(String hcName) {
String hcMethodName = hcName + "." + HC_METHOD_NAME;
String[] reply = null;
log.println("\n### Debugger: testUpWhereCommands");
// run jdb command "where": should list hcMethodName frame
reply = jdb.receiveReplyFor(JdbCommand.where);
if (!checkPattern(reply, hcMethodName)) {
throwFailure("Debugger: jdb command where does not show expected frame: " + hcMethodName);
}
log.println("\nDebugger: jdb command where showed expected frame:\n\t" + hcMethodName);
// run jdb command "up"
jdb.receiveReplyFor(JdbCommand.up);
log.println("\nDebugger: executed jdb command up");
// run jdb command "where": should not list hcMethodName frame
reply = jdb.receiveReplyFor(JdbCommand.where);
if (checkPattern(reply, hcMethodName)) {
throwFailure("Debugger: jdb command where showed unexpected frame: " + hcMethodName);
}
log.println("\nDebugger: jdb command where does not show unexpected frame:\n\t" + hcMethodName);
}
/* Test the jdb commands "down" and "where" for hidden class. */
private void testDownWhereCommands(String hcName) {
String hcMethodName = hcName + "." + HC_METHOD_NAME;
String[] reply = null;
log.println("\n### Debugger: testDownWhereCommands");
// run jdb command "down"
jdb.receiveReplyFor(JdbCommand.down);
log.println("\nDebugger: executed jdb command down");
// run jdb command "where": should list hcMethodName frame again
reply = jdb.receiveReplyFor(JdbCommand.where);
if (!checkPattern(reply, hcMethodName)) {
throwFailure("Debugger: jdb command where does not show expected frame: " + hcMethodName);
}
log.println("\nDebugger: jdb command where showed expected frame:\n\t" + hcMethodName);
}
/* Test the jdb commands "fields" and "methods" for hidden class. */
private void testFieldsMethods(String hcName) {
String[] reply = null;
log.println("\n### Debugger: testFieldsMethods");
// run jdb command "methods" for hidden class
reply = jdb.receiveReplyFor(JdbCommand.methods + hcName);
if (!checkPattern(reply, hcName)) {
throwFailure("Debugger: no expected hidden class name in its methods: " + hcName);
}
log.println("\nDebugger: jdb command \"methods\" showed expected method:\n\t" + HC_METHOD_NAME);
// run jdb command "fields" for hidden class
reply = jdb.receiveReplyFor(JdbCommand.fields + hcName);
if (!checkPattern(reply, HC_FIELD_NAME)) {
throwFailure("Debugger: no expected hidden class field in its fields: " + HC_FIELD_NAME);
}
log.println("\nDebugger: jdb command \"fields\" showed expected field:\n\t" + HC_FIELD_NAME);
}
/* Test the jdb commands "watch" and "unwatch" for hidden class. */
private void testWatchCommands(String hcName) {
String hcFieldName = hcName + "." + HC_FIELD_NAME;
String[] reply = null;
log.println("\n### Debugger: testWatchCommands");
// run jdb command "watch" for hidden class field HC_FIELD_NAME
reply = jdb.receiveReplyFor(JdbCommand.watch + hcFieldName);
if (!checkPattern(reply, HC_FIELD_NAME)) {
throwFailure("Debugger: was not able to set watch point: " + hcFieldName);
}
log.println("\nDebugger: jdb command \"watch\" added expected field to watch:\n\t" + hcFieldName);
// run jdb command "cont"
jdb.receiveReplyFor(JdbCommand.cont);
jdb.receiveReplyFor(JdbCommand.next);
// run jdb command "unwatch" for hidden class field HC_FIELD_NAME
reply = jdb.receiveReplyFor(JdbCommand.unwatch + hcFieldName);
if (!checkPattern(reply, HC_FIELD_NAME)) {
throwFailure("Debugger: expect field name in unwatch reply: " + hcFieldName);
}
log.println("\nDebugger: jdb command \"unwatch\" removed expected field from watch:\n\t" + hcFieldName);
}
/* Test the jdb commands "eval", "print" and "dump" for hidden class. */
private void testEvalCommands(String hcName) {
String hcFieldName = hcName + "." + HC_FIELD_NAME;
String[] reply = null;
log.println("\n### Debugger: testEvalCommands");
// run jdb command "eval" for hidden class field HC_FIELD_NAME
reply = jdb.receiveReplyFor(JdbCommand.eval + hcFieldName);
if (!checkPattern(reply, hcFieldName)) {
throwFailure("Debugger: expected field name in jdb command eval field reply: " + hcFieldName);
}
log.println("\nDebugger: jdb command \"eval\" showed expected hidden class field name:\n\t" + hcFieldName);
// run jdb command "print" for hidden class field HC_FIELD_NAME
reply = jdb.receiveReplyFor(JdbCommand.print + hcFieldName);
if (!checkPattern(reply, hcFieldName)) {
throwFailure("Debugger: expected field name in jdb command print field reply: " + hcFieldName);
}
log.println("\nDebugger: jdb command \"print\" showed expected hidden class field name:\n\t" + hcFieldName);
// execute jdb command "dump" for hidden class field HC_FIELD_NAME
reply = jdb.receiveReplyFor(JdbCommand.dump + hcFieldName);
if (!checkPattern(reply, hcFieldName)) {
throwFailure("Debugger: expected field name in jdb command dump field reply: " + hcFieldName);
}
log.println("\nDebugger: jdb command \"dump\" showed expected hidden class field name:\n\t" + hcFieldName);
}
/* Test the jdb command "watch" with an invalid class name. */
private void testInvWatchCommand(String hcName) {
String hcFieldName = hcName + "." + HC_FIELD_NAME;
String MsgBase = "\nDebugger: jdb command \"watch\" with invalid field " + hcFieldName;
String[] reply = null;
// run jdb command "watch" with an invalid class name
reply = jdb.receiveReplyFor(JdbCommand.watch + hcFieldName);
if (checkPattern(reply, "Deferring watch modification")) {
throwFailure(MsgBase + " must not set deferred watch point");
}
log.println(MsgBase + " did not set deferred watch point");
}
/* Test the jdb command "eval" with an invalid class name. */
private void testInvEvalCommand(String hcName) {
String hcFieldName = hcName + "." + HC_FIELD_NAME;
String MsgBase = "\nDebugger: jdb command \"eval\" with invalid field " + hcFieldName;
String[] reply = null;
// run jdb command "eval" with an invalid class name
reply = jdb.receiveReplyFor(JdbCommand.eval + hcFieldName);
if (!checkPattern(reply, "ParseException")) {
throwFailure(MsgBase + " must be rejected with ParseException");
}
log.println(MsgBase + " was rejected with ParseException");
}
/* Test the jdb commands "watch" and "eval" with various invalid class names. */
private void testInvalidCommands() {
String className = null;
String[] invClassNames = {
"xx.yyy/0x111/0x222",
"xx./0x111.0x222",
"xx.yyy.zzz/"
};
log.println("\n### Debugger: testInvalidCommands");
// run jdb commands "watch" and "eval" with invalid class names
for (int idx = 0; idx < invClassNames.length; idx++) {
className = invClassNames[idx];
testInvWatchCommand(className + "." + HC_FIELD_NAME);
testInvEvalCommand(className + "." + HC_FIELD_NAME);
}
}
/* Main testing method. */
protected void runCases() {
String hcName = runPrologue();
testClassCommands(hcName);
stopInHiddenClassMethod(hcName);
testUpWhereCommands(hcName);
testDownWhereCommands(hcName);
testFieldsMethods(hcName);
testWatchCommands(hcName);
testEvalCommands(hcName);
testInvalidCommands();
jdb.contToExit(1);
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package nsk.jdb.hidden_class.hc001;
import java.io.File;
import java.io.PrintStream;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.nio.file.Files;
import java.nio.file.Paths;
import nsk.share.jdb.*;
/* Interface for tested hidden class to implement. */
interface HCInterf {
void hcMethod();
}
/* Hidden class definition used to define tested hidden class
* with lookup.defineHiddenClass. */
class HiddenClass implements HCInterf {
static String hcField = null;
private String getClassName() {
return this.getClass().getName();
}
public void hcMethod() {
hcField = getClassName();
if (hcField.indexOf("HiddenClass") == -1) {
throw new RuntimeException("Debuggee: Unexpected HiddenClass name: " + hcField);
}
}
}
/* This is debuggee aplication */
public class hc001a {
static PrintStream log = null;
static void logMsg(String msg) { log.println(msg); log.flush(); }
static final String JAVA_CP = System.getProperty("java.class.path");
static final String HC_NAME = HiddenClass.class.getName().replace(".", File.separator) + ".class";
static final String HC_PATH = getClassPath(HiddenClass.class);
static String hcName = null; // the debugger gets value of this field
static String getClassPath(Class<?> klass) {
String classPath = klass.getTypeName().replace(".", File.separator) + ".class";
for (String path: JAVA_CP.split(File.pathSeparator)) {
String fullClassPath = path + File.separator + classPath;
if (new File(fullClassPath).exists()) {
return fullClassPath;
}
}
throw new RuntimeException("class path for " + klass.getName() + " not found");
}
public static void main(String args[]) throws Exception {
// The Jdb framework uses stdout for commands reply,
// so it is not usable for normal logging.
// Use a separate log file for debuggee located in JTwork/scratch.
log = new PrintStream("Debuggee.log");
hc001a testApp = new hc001a();
int status = testApp.runIt(args);
System.exit(hc001.JCK_STATUS_BASE + status);
}
// This method is to hit a breakpoint at expected execution point.
void emptyMethod() {}
public int runIt(String args[]) throws Exception {
JdbArgumentHandler argumentHandler = new JdbArgumentHandler(args);
logMsg("Debuggee: runIt: started");
logMsg("Debuggee: JAVA_CP: " + JAVA_CP);
logMsg("Debuggee: HC_NAME: " + HC_NAME);
logMsg("Debuggee: HC_PATH: " + HC_PATH);
// Define tested hidden class.
Class<?> hc = defineHiddenClass(HC_PATH);
// A hidden class name has an unpredictable suffix at the end,
// and so, can not be hard coded and known to debugger in advance.
// Store the hidden class name in field, so the debugger can read it from there.
hcName = hc.getName();
logMsg("Debuggee: Defined HiddenClass: " + hcName);
// It is impossible to use a hidden class name to define a variable,
// so we use the interface which the tested hidden class implements.
HCInterf hcObj = (HCInterf)hc.newInstance();
logMsg("Debuggee: created an instance of a hidden class: " + hcName);
// It is for debuuger to set a breakpoint at a well known execution point.
logMsg("Debuggee: invoking emptyMethod to hit expected breakpoint");
emptyMethod();
// Invoke a hidden class method.
logMsg("Debuggee: invoking a method of a hidden class: " + hcName);
hcObj.hcMethod();
logMsg("Debuggee: runIt finished");
return hc001.PASSED;
}
static Class<?> defineHiddenClass(String classFileName) throws Exception {
try {
Lookup lookup = MethodHandles.lookup();
byte[] bytes = Files.readAllBytes(Paths.get(classFileName));
// The class name from class file is a normal binary name but the
// defineHiddenClass appends a suffix "/<unqualified-name>" to it,
// so it is not a valid binary name anymore.
Class<?> hc = lookup.defineHiddenClass(bytes, false).lookupClass();
return hc;
} catch (Exception ex) {
logMsg("Debuggee: defineHiddenClass: caught Exception " + ex.getMessage());
ex.printStackTrace(log);
log.flush();
throw ex;
}
}
}