8341495: JShell crashes with java.util.MissingFormatArgumentException

Reviewed-by: jlahoda
This commit is contained in:
Archie Cobbs 2024-12-04 23:16:50 +00:00
parent ba158edd81
commit bcebb0c53c
3 changed files with 89 additions and 28 deletions

View File

@ -72,6 +72,7 @@ import java.util.Scanner;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -805,46 +806,46 @@ public class JShellTool implements MessageHandler {
}
/**
* Add normal prefixing/postfixing to embedded newlines in a string,
* Add normal prefixing/postfixing to embedded newlines in a format string,
* bracketing with normal prefix/postfix
*
* @param s the string to prefix
* @return the pre/post-fixed and bracketed string
* @param format the format string to prefix/postfix
* @return the pre/post-fixed and bracketed format string
*/
String prefix(String s) {
return prefix(s, feedback.getPre(), feedback.getPost());
String prefix(String format) {
return prefix(format, feedback.getPre(), feedback.getPost());
}
/**
* Add error prefixing/postfixing to embedded newlines in a string,
* Add error prefixing/postfixing to embedded newlines in a format string,
* bracketing with error prefix/postfix
*
* @param s the string to prefix
* @return the pre/post-fixed and bracketed string
* @param format the format string to prefix/postfix
* @return the pre/post-fixed and bracketed format string
*/
String prefixError(String s) {
return prefix(s, feedback.getErrorPre(), feedback.getErrorPost());
String prefixError(String format) {
return prefix(format, feedback.getErrorPre(), feedback.getErrorPost());
}
/**
* Add prefixing/postfixing to embedded newlines in a string,
* Add prefixing/postfixing to embedded newlines in a format string,
* bracketing with prefix/postfix. No prefixing when non-interactive.
* Result is expected to be the format for a printf.
* Both input and result strings are expected to be the format for a printf.
*
* @param s the string to prefix
* @param pre the string to prepend to each line
* @param post the string to append to each line (replacing newline)
* @return the pre/post-fixed and bracketed string
* @param format the format string to prefix
* @param pre the string to prepend to each line (printf safe)
* @param post the string to append to each line (replacing newline; printf safe)
* @return the pre/post-fixed and bracketed format string
*/
String prefix(String s, String pre, String post) {
if (s == null) {
String prefix(String format, String pre, String post) {
if (format == null) {
return "";
}
if (!interactiveModeBegun) {
// messages expect to be new-line terminated (even when not prefixed)
return s + "%n";
return format + "%n";
}
String pp = s.replaceAll("\\R", post + pre);
String pp = format.replaceAll("\\R", post + pre);
if (pp.endsWith(post + pre)) {
// prevent an extra prefix char and blank line when the string
// already terminates with newline
@ -859,7 +860,7 @@ public class JShellTool implements MessageHandler {
* @param key the resource key
*/
void hardrb(String key) {
hard(getResourceString(key));
hard(escape(getResourceString(key)));
}
/**
@ -882,7 +883,7 @@ public class JShellTool implements MessageHandler {
*/
@Override
public void hardmsg(String key, Object... args) {
hard(messageFormat(key, args));
hard(escape(messageFormat(key, args)));
}
/**
@ -912,7 +913,7 @@ public class JShellTool implements MessageHandler {
}
<T> void hardPairs(Stream<T> stream, Function<T, String> a, Function<T, String> b) {
Map<String, String> a2b = stream.collect(toMap(a, b,
Map<String, String> a2b = stream.collect(toMap(a, b.andThen(this::escape),
(m1, m2) -> m1,
LinkedHashMap::new));
for (Entry<String, String> e : a2b.entrySet()) {
@ -921,6 +922,13 @@ public class JShellTool implements MessageHandler {
}
}
/**
* Escape '%' signs in a plain string to make it a valid format string.
*/
String escape(Object s) {
return s.toString().replace("%", "%%");
}
/**
* Trim whitespace off end of string
*
@ -2332,7 +2340,7 @@ public class JShellTool implements MessageHandler {
sb.append(startup.show(false));
sb.append(startup.showDetail());
}
hard(sb.toString());
hard(escape(sb));
}
private void showIndent() {
@ -3262,7 +3270,7 @@ public class JShellTool implements MessageHandler {
sb.append(a);
}
if (sb.length() > 0) {
hard(sb.toString());
hard(escape(sb));
}
return false;
}
@ -3777,7 +3785,7 @@ public class JShellTool implements MessageHandler {
case NONEXISTENT:
default:
// Should not occur
error("Unexpected status: " + previousStatus.toString() + "=>" + status.toString());
error("Unexpected status: %s=>%s", previousStatus, status);
act = FormatAction.DROPPED;
}
return act;

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2016, 2024, 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
@ -1052,7 +1052,7 @@ Show the normal prompt and the continuation-prompts:\n\
\n\
Where <mode> is the name of a previously defined feedback mode.\n\
Where <prompt> and <continuation-prompt> are quoted strings to be printed as input prompts.\n\
Both may optionally contain '%%s' which will be substituted with the next snippet ID --\n\
Both may optionally contain '%s' which will be substituted with the next snippet ID --\n\
note that what is entered may not be assigned that ID, for example it may be an error or command.\n\
The continuation-prompt is used on the second and subsequent lines of a multi-line snippet.\n\
\n\

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2024, 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 8341495
* @summary Test for crash caused by format specifier in startup script
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.jshell/jdk.internal.jshell.tool
* @library /tools/lib
* @build toolbox.ToolBox
* @build KullaTesting Compiler
* @run testng StartupWithFormatSpecifierTest
*/
import java.nio.file.Path;
import org.testng.annotations.Test;
@Test
public class StartupWithFormatSpecifierTest extends ReplToolTesting {
public void testStartupWithFormatSpecifier() {
Compiler compiler = new Compiler();
String startupScript = "String.format(\"This is a %s.\", \"test\");";
Path startupFile = compiler.getPath("StartupFileOption/startup.txt");
compiler.writeToFile(startupFile, startupScript);
test(new String[] { "--startup", startupFile.toString() },
(a) -> assertCommandOutputContains(a, "/set start", startupScript)
);
}
}