* Copyright (c) 2023, 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 8303623
* @modules jdk.compiler/com.sun.tools.javac.code
* @summary Compiler should disallow non-standard UTF-8 string encodings
*/
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.Main;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.util.Arrays;
public class InvalidModifiedUtf8Test {
private static final String SOURCE_0 = """
interface CLASSNAME {
void ABC\u00e8(); // encodes to: 41 42 43 c3 a8
}
""";
private static final String SOURCE_1 = """
interface CLASSNAME {
void ABC\u1000(); // encodes to: 41 42 43 e1 80 80
}
""";
private static final String SOURCE_2 = """
interface CLASSNAME {
void ABC\u0100(); // encodes to: 41 42 43 c4 00
}
""";
private static final String SOURCE_3 = """
interface CLASSNAME {
void ABC\u1000(); // encodes to: 41 42 43 e1 80 80
}
""";
private static final String SOURCE_4 = """
interface CLASSNAME {
void ABCD(); // encodes to: 41 42 43 44
}
""";
private static final String SOURCE_5 = """
interface CLASSNAME {
void ABCD(); // encodes to: 41 42 43 44
}
""";
private static final String REF_SOURCE = """
interface RefClass extends CLASSNAME {
}
""";
private static TestCase[] TEST_CASES = new TestCase[] {
new TestCase(0, SOURCE_0, "414243c3a8", "414243c3e8"),
new TestCase(1, SOURCE_1, "414243e18080", "414243e18040"),
new TestCase(2, SOURCE_2, "414243c480", "414243c181"),
new TestCase(3, SOURCE_3, "414243e18080", "414243e08480"),
new TestCase(4, SOURCE_4, "41424344", "41424300"),
new TestCase(5, SOURCE_5, "41424344", "4142e180"),
};
public static String bytes2string(byte[] array) {
char[] buf = new char[array.length * 2];
for (int i = 0; i < array.length; i++) {
int value = array[i] & 0xff;
buf[i * 2] = Character.forDigit(value >> 4, 16);
buf[i * 2 + 1] = Character.forDigit(value & 0xf, 16);
}
return new String(buf);
}
public static byte[] string2bytes(String string) {
byte[] buf = new byte[string.length() / 2];
for (int i = 0; i < string.length(); i += 2) {
int value = Integer.parseInt(string.substring(i, i + 2), 16);
buf[i / 2] = (byte)value;
}
return buf;
}
private static void createSourceFile(String content, File file) throws IOException {
System.err.println("creating: " + file);
try (PrintStream output = new PrintStream(new FileOutputStream(file))) {
output.println(content);
}
}
private static void writeFile(File file, byte[] content) throws IOException {
System.err.println("writing: " + file);
try (FileOutputStream output = new FileOutputStream(file)) {
Files.write(file.toPath(), content);
}
}
private static void compileRefClass(File file, boolean expectSuccess, String expectedError) {
final StringWriter diags = new StringWriter();
final String[] params = new String[] {
"-classpath",
".",
"-XDrawDiagnostics",
file.toString()
};
System.err.println("compiling: " + file);
int ret = Main.compile(params, new PrintWriter(diags, true));
System.err.println("exit value: " + ret);
String output = diags.toString().trim();
if (!output.isEmpty())
System.err.println("output:\n" + output);
else
System.err.println("no output");
if (!expectSuccess && ret == 0)
throw new AssertionError("compilation succeeded, but expected failure");
else if (expectSuccess && ret != 0)
throw new AssertionError("compilation failed, but expected success");
if (expectedError != null && !diags.toString().contains(expectedError))
throw new AssertionError("expected output \"" + expectedError + "\" not found");
}
public static void main(String... args) throws Exception {
for (TestCase test : TEST_CASES)
test.createSourceFile();
for (TestCase test : TEST_CASES) {
int ret = Main.compile(new String[] { test.sourceFile().toString() });
if (ret != 0)
throw new AssertionError("compilation of " + test.sourceFile() + " failed");
}
final boolean expectSuccess = Source.DEFAULT.compareTo(Source.JDK21) <= 0;
for (TestCase test : TEST_CASES) {
System.err.println("==== TEST " + test.index() + " ====");
final File refSource = new File("RefClass.java");
createSourceFile(REF_SOURCE.replaceAll("CLASSNAME", test.className()), refSource);
compileRefClass(refSource, true, null);
System.err.println("modifying: " + test.classFile());
final File classFile = test.classFile();
final byte[] data1 = Files.readAllBytes(classFile.toPath());
final byte[] data2 = test.modify(data1);
writeFile(classFile, data2);
compileRefClass(refSource, expectSuccess, "compiler.misc.bad.utf8.byte.sequence.at");
}
}
static class TestCase {
final int index;
final String source;
final String match;
final String replace;
TestCase(int index, String source, String match, String replace) {
this.index = index;
this.source = source.replaceAll("CLASSNAME", className());
this.match = match;
this.replace = replace;
}
byte[] modify(byte[] input) {
final byte[] output = string2bytes(bytes2string(input).replaceAll(match, replace));
if (Arrays.equals(output, input))
throw new AssertionError("modification of " + classFile() + " failed");
return output;
}
int index() {
return index;
}
String className() {
return "Class" + index;
}
File sourceFile() {
return new File(className() + ".java");
}
File classFile() {
return new File(className() + ".class");
}
void createSourceFile() throws IOException {
InvalidModifiedUtf8Test.createSourceFile(source, sourceFile());
}
}
}