8185108: JShell: NullPointerException when throwing exception with null message under local ExecutionControl
Reviewed-by: jlahoda
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Wed Aug 23 10:53:57 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Wed Aug 23 14:06:50 2017 -0700
@@ -574,7 +574,7 @@
DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(ex.id());
exception = new UnresolvedReferenceException(sn, translateExceptionStack(ex));
} catch (UserException ex) {
- exception = new EvalException(translateExceptionMessage(ex),
+ exception = new EvalException(ex.getMessage(),
ex.causeExceptionClass(),
translateExceptionStack(ex));
} catch (RunException ex) {
@@ -782,13 +782,6 @@
return elems;
}
- private String translateExceptionMessage(Exception ex) {
- String msg = ex.getMessage();
- return msg.equals("<none>")
- ? null
- : msg;
- }
-
private boolean isWrap(StackTraceElement ste) {
return PREFIX_PATTERN.matcher(ste.getClassName()).find();
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java Wed Aug 23 10:53:57 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java Wed Aug 23 14:06:50 2017 -0700
@@ -45,6 +45,12 @@
class ExecutionControlForwarder {
/**
+ * Represent null in a streamed UTF string. Vanishingly improbable string to
+ * occur in a user string.
+ */
+ static final String NULL_MARKER = "\u0002*\u03C0*NULL*\u03C0*\u0003";
+
+ /**
* Maximum number of characters for writeUTF(). Byte maximum is 65535, at
* maximum three bytes per character that is 65535 / 3 == 21845. Minus one
* for safety.
@@ -93,6 +99,10 @@
out.writeInt(i);
}
+ private void writeNullOrUTF(String s) throws IOException {
+ writeUTF(s == null ? NULL_MARKER : s);
+ }
+
private void writeUTF(String s) throws IOException {
if (s == null) {
s = "";
@@ -197,7 +207,7 @@
return true;
} catch (UserException ex) {
writeStatus(RESULT_USER_EXCEPTION);
- writeUTF(ex.getMessage());
+ writeNullOrUTF(ex.getMessage());
writeUTF(ex.causeExceptionClass());
writeObject(ex.getStackTrace());
flush();
@@ -213,8 +223,10 @@
flush();
return true;
} catch (Throwable ex) {
+ // Unexpected exception, have something in the message
writeStatus(RESULT_TERMINATED);
- writeUTF(ex.getMessage());
+ String msg = ex.getMessage();
+ writeUTF(msg == null? ex.toString() : msg);
flush();
return false;
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java Wed Aug 23 10:53:57 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java Wed Aug 23 14:06:50 2017 -0700
@@ -29,6 +29,7 @@
import java.io.ObjectOutput;
import jdk.jshell.JShellException;
import jdk.jshell.spi.ExecutionControl;
+import static jdk.jshell.execution.ExecutionControlForwarder.NULL_MARKER;
import static jdk.jshell.execution.RemoteCodes.*;
/**
@@ -185,6 +186,16 @@
}
/**
+ * Read a UTF or a null encoded as a null marker.
+ * @return a string or null
+ * @throws IOException passed through from readUTF()
+ */
+ private String readNullOrUTF() throws IOException {
+ String s = in.readUTF();
+ return s.equals(NULL_MARKER) ? null : s;
+ }
+
+ /**
* Reports results from a remote agent command that does not expect
* exceptions.
*/
@@ -273,7 +284,7 @@
}
case RESULT_USER_EXCEPTION: {
// A user exception was encountered.
- String message = in.readUTF();
+ String message = readNullOrUTF();
String exceptionClassName = in.readUTF();
StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
throw new UserException(message, exceptionClassName, elems);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExceptionMessageTest.java Wed Aug 23 14:06:50 2017 -0700
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2017, 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
+ * @bug 8185108
+ * @summary Test exception().getMessage() in events returned by eval()
+ * @run testng ExceptionMessageTest
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+
+import jdk.jshell.JShell;
+import jdk.jshell.SnippetEvent;
+import jdk.jshell.execution.DirectExecutionControl;
+import jdk.jshell.execution.JdiExecutionControlProvider;
+import jdk.jshell.execution.LocalExecutionControlProvider;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControlProvider;
+import jdk.jshell.spi.ExecutionEnv;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class ExceptionMessageTest {
+
+ public void testDefaultEC() {
+ doTestCases(new JdiExecutionControlProvider(), "default");
+ }
+
+ public void testLocalEC() {
+ doTestCases(new LocalExecutionControlProvider(), "local");
+ }
+
+ public void testDirectEC() {
+ doTestCases(new ExecutionControlProvider() {
+ public ExecutionControl generate(ExecutionEnv env, Map<String, String> param) throws Throwable {
+ return new DirectExecutionControl();
+ }
+
+ public String name() {
+ return "direct";
+ }
+
+ }, "direct");
+ }
+
+ private JShell shell(ExecutionControlProvider ec) {
+ return JShell.builder().executionEngine(ec, new HashMap<>()).build();
+ }
+
+ private void doTestCases(ExecutionControlProvider ec, String label) {
+ JShell jshell = shell(ec);
+ doTest(jshell, label, "throw new java.io.IOException();", null);
+ doTest(jshell, label, "throw new java.io.IOException((String)null);", null);
+ doTest(jshell, label, "throw new java.io.IOException(\"\");", "");
+ doTest(jshell, label, "throw new java.io.IOException(\"test\");", "test");
+ }
+
+ private void doTest(JShell jshell, String label, String code, String expected) {
+ List<SnippetEvent> result = jshell.eval(code);
+ assertEquals(result.size(), 1, "Expected only one event");
+ SnippetEvent evt = result.get(0);
+ Exception exc = evt.exception();
+ String out = exc.getMessage();
+ assertEquals(out, expected, "Exception message not as expected: " +
+ label + " -- " + code);
+ }
+}