1 /* 2 * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8279822 27 * @requires vm.flagless 28 * @library /test/lib 29 * @modules java.base/jdk.internal.org.objectweb.asm 30 * 31 * @run main compiler.runtime.TestConstantsInError 32 */ 33 package compiler.runtime; 34 35 import jdk.internal.org.objectweb.asm.*; 36 import jdk.test.lib.Platform; 37 import jdk.test.lib.process.OutputAnalyzer; 38 import jdk.test.lib.process.ProcessTools; 39 40 import java.io.FileOutputStream; 41 import java.io.IOException; 42 import java.lang.invoke.MethodHandle; 43 import java.lang.invoke.MethodHandleProxies; 44 import java.lang.invoke.MethodHandles; 45 import java.lang.invoke.MethodType; 46 import java.util.ArrayList; 47 import java.util.List; 48 49 import static jdk.internal.org.objectweb.asm.ClassWriter.*; 50 import static jdk.internal.org.objectweb.asm.Opcodes.*; 51 52 interface OutputProcessor { 53 default void process(OutputAnalyzer output, boolean isC1) {} 54 } 55 56 public abstract class TestConstantsInError implements OutputProcessor { 57 static final String TEST_PREFIX = class2desc(TestConstantsInError.class) + "$Test"; 58 59 public interface Test extends Runnable {} 60 61 62 interface Generator { 63 void generate(MethodVisitor mv); 64 } 65 66 static String class2desc(Class<?> cls) { 67 return cls.getName().replace('.', '/'); 68 } 69 70 public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator; 71 72 static byte[] generateClassFile(String suffix, Generator g) throws IOException { 73 var cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); 74 String name = TEST_PREFIX + "_" + suffix; 75 cw.visit(V17, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null); 76 77 { 78 var mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "()V", null, null); 79 mv.visitCode(); 80 g.generate(mv); 81 mv.visitInsn(RETURN); 82 mv.visitMaxs(0, 0); 83 } 84 byte[] classFile = cw.toByteArray(); 85 86 try (FileOutputStream fos = new FileOutputStream(PATH + name + ".class")) { 87 fos.write(classFile); 88 } 89 90 return classFile; 91 } 92 93 static Test generate(String suffix, Class<? extends LinkageError> expectedError, Generator g) { 94 try { 95 byte[] classFile = generateClassFile(suffix, g); 96 MethodHandles.Lookup testLookup = MethodHandles.lookup().defineHiddenClass(classFile, true); 97 MethodHandle testMH = testLookup.findStatic(testLookup.lookupClass(), "test", MethodType.methodType(void.class)); 98 99 testMH = MethodHandles.filterReturnValue(testMH, 100 MethodHandles.insertArguments( 101 MethodHandles.throwException(void.class, AssertionError.class), 102 0, new AssertionError("no exception thrown"))); 103 104 // Install empty handler for linkage exceptions. 105 testMH = MethodHandles.catchException(testMH, expectedError, 106 MethodHandles.empty(MethodType.methodType(void.class, expectedError))); 107 108 return MethodHandleProxies.asInterfaceInstance(Test.class, testMH); 109 } catch (Throwable e) { 110 throw new InternalError(e); 111 } 112 } 113 114 static void run(String name, Class<? extends LinkageError> expectedError, Generator g) { 115 Test test = generate(name, expectedError, g); 116 for (int i = 0; i < 1000; i++) { 117 test.run(); 118 } 119 } 120 121 static class TestConstantClass extends TestConstantsInError { 122 public static void main(String[] args) { 123 run("C1", NoClassDefFoundError.class, mv -> mv.visitLdcInsn(Type.getType("LUnknownClass;"))); // non-existent class 124 run("C2", IllegalAccessError.class, mv -> mv.visitLdcInsn(Type.getType("Ljava/lang/invoke/LambdaForm;"))); // inaccessible 125 126 // class loader constraints? 127 } 128 129 public void process(OutputAnalyzer results, boolean isC1) { 130 results.shouldMatch("Test_C1/.*::test \\(3 bytes\\)$") 131 .shouldMatch("Test_C2/.*::test \\(3 bytes\\)$"); 132 133 if (isC1 && Platform.isAArch64()) { // no code patching 134 results.shouldMatch("Test_C1/.*::test \\(3 bytes\\) made not entrant") 135 .shouldMatch("Test_C2/.*::test \\(3 bytes\\) made not entrant"); 136 } else { 137 results.shouldNotContain("made not entrant"); 138 } 139 } 140 141 public void processC2(OutputAnalyzer results) { 142 results.shouldNotContain("made not entrant"); 143 } 144 } 145 146 static class TestConstantMethodHandle extends TestConstantsInError { 147 public static void main(String[] args) { 148 // Non-existent holder class 149 run("MH1", NoClassDefFoundError.class, 150 mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "UnknownClass", "ignored", "()V", false))); 151 152 // Inaccessible holder class 153 run("MH2", IllegalAccessError.class, 154 mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaForm", "ignored", "()V", false))); 155 156 // Method vs InterfaceMethod mismatch 157 run("MH3", IncompatibleClassChangeError.class, 158 mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "java/lang/Object", "ignored", "()V", true))); 159 160 // Non-existent method 161 run("MH4", NoSuchMethodError.class, 162 mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "java/lang/Object", "cast", "()V", false))); 163 } 164 165 public void process(OutputAnalyzer results, boolean isC1) { 166 results.shouldMatch("Test_MH1/.*::test \\(3 bytes\\)$") 167 .shouldMatch("Test_MH2/.*::test \\(3 bytes\\)$") 168 .shouldMatch("Test_MH3/.*::test \\(3 bytes\\)$") 169 .shouldMatch("Test_MH4/.*::test \\(3 bytes\\)$"); 170 171 if (isC1 && Platform.isAArch64()) { // no code patching 172 results.shouldMatch("Test_MH1/.*::test \\(3 bytes\\) made not entrant") 173 .shouldMatch("Test_MH2/.*::test \\(3 bytes\\) made not entrant") 174 .shouldMatch("Test_MH3/.*::test \\(3 bytes\\) made not entrant") 175 .shouldMatch("Test_MH4/.*::test \\(3 bytes\\) made not entrant"); 176 } else { 177 results.shouldNotContain("made not entrant"); 178 } 179 } 180 } 181 182 static class TestConstantMethodType extends TestConstantsInError { 183 public static void main(String[] args) { 184 run("MT1", NoClassDefFoundError.class, 185 mv -> mv.visitLdcInsn(Type.getMethodType("(LUnknownClass;)V"))); 186 run("MT2", NoClassDefFoundError.class, 187 mv -> mv.visitLdcInsn(Type.getMethodType("()LUnknownClass;"))); 188 } 189 190 public void process(OutputAnalyzer results, boolean isC1) { 191 results.shouldMatch("Test_MT1/.*::test \\(3 bytes\\)$") 192 .shouldMatch("Test_MT2/.*::test \\(3 bytes\\)$"); 193 194 if (isC1 && Platform.isAArch64()) { // no code patching 195 results.shouldMatch("Test_MT1/.*::test \\(3 bytes\\) made not entrant") 196 .shouldMatch("Test_MT2/.*::test \\(3 bytes\\) made not entrant"); 197 } else { 198 results.shouldNotContain("made not entrant"); 199 } 200 } 201 } 202 203 static class TestConstantDynamic extends TestConstantsInError { 204 static int bsm1() throws Exception { 205 throw new AssertionError("should not be invoked"); 206 } 207 208 static int bsm2(MethodHandles.Lookup lookup, String name, Class c) throws Exception { 209 throw new Exception("expected"); 210 } 211 212 static final Handle BSM1 = new Handle(H_INVOKESTATIC, class2desc(TestConstantDynamic.class), "bsm1", "()I", false); 213 static final Handle BSM2 = new Handle(H_INVOKESTATIC, class2desc(TestConstantDynamic.class), "bsm2", 214 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)I", 215 false); 216 217 public static void main(String[] args) { 218 run("CD1", NoClassDefFoundError.class, 219 mv -> { 220 Handle bsm = new Handle(H_INVOKESTATIC, "UnknownClass", "unknown", "()LUnknownClass;", false); 221 mv.visitLdcInsn(new ConstantDynamic("tmp", "LUnknownClass;", bsm)); 222 }); 223 run("CD2", NoSuchMethodError.class, 224 mv -> { 225 Handle bsm = new Handle(H_INVOKESTATIC, class2desc(TestConstantDynamic.class), "unknown", "()I", false); 226 mv.visitLdcInsn(new ConstantDynamic("tmp", "LUnknownClass;", bsm)); 227 }); 228 run("CD3", BootstrapMethodError.class, mv -> mv.visitLdcInsn(new ConstantDynamic("tmp", "I", BSM1))); 229 run("CD4", BootstrapMethodError.class, mv -> mv.visitLdcInsn(new ConstantDynamic("tmp", "I", BSM2))); 230 } 231 232 public void process(OutputAnalyzer results, boolean isC1) { 233 if (isC1) { 234 results.shouldMatch("Test_CD1.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant") 235 .shouldMatch("Test_CD2.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant") 236 .shouldMatch("Test_CD3.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant") 237 .shouldMatch("Test_CD4.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant"); 238 } else { 239 results.shouldMatch("Test_CD1.*::test \\(3 bytes\\)$") 240 .shouldMatch("Test_CD2.*::test \\(3 bytes\\)$") 241 .shouldMatch("Test_CD3.*::test \\(3 bytes\\)$") 242 .shouldMatch("Test_CD4.*::test \\(3 bytes\\)$"); 243 } 244 } 245 } 246 247 static void run(TestConstantsInError test) throws Exception { 248 List<String> commonArgs = List.of( 249 "--add-exports", "java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED", 250 "-Xbatch", "-XX:CompileThreshold=100", 251 "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,*::test", 252 "-XX:+PrintCompilation", 253 "-XX:CompileCommand=print,*::test", 254 "-Dtest.classes=" + System.getProperty("test.classes", "."), 255 "-XX:+IgnoreUnrecognizedVMOptions", 256 test.getClass().getName()); 257 258 ArrayList<String> c1Args = new ArrayList<>(); 259 c1Args.addAll(List.of("-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1", "-XX:+TracePatching")); 260 c1Args.addAll(commonArgs); 261 262 OutputAnalyzer outputC1 = ProcessTools.executeTestJvm(c1Args) 263 .shouldHaveExitValue(0); 264 265 test.process(outputC1, true); 266 267 ArrayList<String> c2Args = new ArrayList<>(); 268 c2Args.add("-XX:-TieredCompilation"); 269 c2Args.addAll(commonArgs); 270 271 OutputAnalyzer outputC2 = ProcessTools.executeTestJvm(c2Args) 272 .shouldHaveExitValue(0); 273 274 test.process(outputC2, false); 275 } 276 277 public static void main(String[] args) throws Exception { 278 run(new TestConstantClass()); 279 run(new TestConstantMethodType()); 280 run(new TestConstantMethodHandle()); 281 run(new TestConstantDynamic()); 282 } 283 }