c1f5ca115d
Reviewed-by: vromero
253 lines
9.2 KiB
Java
253 lines
9.2 KiB
Java
/*
|
|
* 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 {
|
|
|
|
//
|
|
// What this test does (repeatedly):
|
|
// 1. Compile a Java source file for ClassX normally
|
|
// 2. Modify the UTF-8 inside the ClassX classfile so that it is
|
|
// still valid structurally but uses a non-standard way of
|
|
// encoding some character (according to "Modified UTF-8").
|
|
// 3. Compile a Java source file for RefClass that references ClassX
|
|
// 4. Verify that the compiler gives a "bad UTF-8" error
|
|
//
|
|
|
|
// We change c3 a8 -> c3 e8 (illegal second byte not of the form 0x10xxxxxx)
|
|
private static final String SOURCE_0 = """
|
|
interface CLASSNAME {
|
|
void ABC\u00e8(); // encodes to: 41 42 43 c3 a8
|
|
}
|
|
""";
|
|
|
|
// We change e1 80 80 -> e1 80 40 (illegal third byte not of the form 0x10xxxxxx)
|
|
private static final String SOURCE_1 = """
|
|
interface CLASSNAME {
|
|
void ABC\u1000(); // encodes to: 41 42 43 e1 80 80
|
|
}
|
|
""";
|
|
|
|
// We change c4 80 -> c1 81 (illegal two-byte encoding for one-byte value)
|
|
private static final String SOURCE_2 = """
|
|
interface CLASSNAME {
|
|
void ABC\u0100(); // encodes to: 41 42 43 c4 00
|
|
}
|
|
""";
|
|
|
|
// We change e1 80 80 -> e0 84 80 (illegal three-byte encoding for two-byte value)
|
|
private static final String SOURCE_3 = """
|
|
interface CLASSNAME {
|
|
void ABC\u1000(); // encodes to: 41 42 43 e1 80 80
|
|
}
|
|
""";
|
|
|
|
// We change 44 -> 00 (illegal one-byte encoding of 0x0000)
|
|
private static final String SOURCE_4 = """
|
|
interface CLASSNAME {
|
|
void ABCD(); // encodes to: 41 42 43 44
|
|
}
|
|
""";
|
|
|
|
// We change 43 44 -> e1 80 (illegal truncated three-byte encoding)
|
|
private static final String SOURCE_5 = """
|
|
interface CLASSNAME {
|
|
void ABCD(); // encodes to: 41 42 43 44
|
|
}
|
|
""";
|
|
|
|
// This is the source file that references one of the above
|
|
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 {
|
|
|
|
// Create source files
|
|
for (TestCase test : TEST_CASES)
|
|
test.createSourceFile();
|
|
|
|
// Compile source files
|
|
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");
|
|
}
|
|
|
|
// We should get warnings in JDK 21 and errors in any later release
|
|
final boolean expectSuccess = Source.DEFAULT.compareTo(Source.JDK21) <= 0;
|
|
|
|
// Now compile REF_SOURCE against each classfile without and then with the modification.
|
|
// When compiling without the modification, everything should be normal.
|
|
// When compiling with the modification, an error should be generated.
|
|
for (TestCase test : TEST_CASES) {
|
|
System.err.println("==== TEST " + test.index() + " ====");
|
|
|
|
// Create reference source file
|
|
final File refSource = new File("RefClass.java");
|
|
createSourceFile(REF_SOURCE.replaceAll("CLASSNAME", test.className()), refSource);
|
|
|
|
// Do a normal compilation
|
|
compileRefClass(refSource, true, null);
|
|
|
|
// Now corrupt the class file
|
|
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);
|
|
|
|
// Do a corrupt compilation
|
|
compileRefClass(refSource, expectSuccess, "compiler.misc.bad.utf8.byte.sequence.at");
|
|
}
|
|
}
|
|
|
|
// TestCase
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|