8185108: JShell: NullPointerException when throwing exception with null message under local ExecutionControl
authorrfield
Wed, 23 Aug 2017 14:06:50 -0700
changeset 46926 25ac5da6502e
parent 46925 c538fe357ff9
child 46927 07c0851a4da1
8185108: JShell: NullPointerException when throwing exception with null message under local ExecutionControl Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java
langtools/test/jdk/jshell/ExceptionMessageTest.java
--- 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);
+    }
+}