/* * Copyright (c) 2013, 2018, 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. */ package vm.runtime.defmeth.shared; import jdk.internal.org.objectweb.asm.Handle; import jdk.internal.org.objectweb.asm.Type; import nsk.share.TestFailure; import nsk.share.test.TestUtils; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import static jdk.internal.org.objectweb.asm.Opcodes.*; import jdk.internal.org.objectweb.asm.ClassWriter; import static jdk.internal.org.objectweb.asm.ClassWriter.*; import vm.runtime.defmeth.shared.data.*; import vm.runtime.defmeth.shared.data.method.*; import vm.runtime.defmeth.shared.data.method.body.*; import vm.runtime.defmeth.shared.data.method.param.*; import vm.runtime.defmeth.shared.data.method.result.*; import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import static vm.runtime.defmeth.shared.ExecutionMode.*; /** * Constructs class file from {@code Clazz} instance. */ public class ClassFileGenerator implements Visitor { private final ExecutionMode invocationType; /** Default major version for generated class files * Used when a class doesn't specify what major version should be specified. */ private final int defaultMajorVer; /** Default access flags for generated class files * Used when a class doesn't specify it's own access flags. */ private final int defaultClassAccFlags; /** Represent current state of class file traversal. * Used to avoid passing instances around. */ private ClassWriter cw; private MethodVisitor mv; private Tester t; private String className; public ClassFileGenerator() { this.defaultMajorVer = 52; this.defaultClassAccFlags = ACC_PUBLIC; this.invocationType = ExecutionMode.DIRECT; } public ClassFileGenerator(int ver, int acc, ExecutionMode invocationType) { this.defaultMajorVer = ver; this.defaultClassAccFlags = acc; this.invocationType = invocationType; } /** * Produce constructed class file as a {@code byte[]}. * * @return */ public byte[] getClassFile() { return cw.toByteArray(); } /** * Push integer constant on stack. * * Choose most suitable bytecode to represent integer constant on stack. * * @param value */ private void pushIntConst(int value) { switch (value) { case 0: mv.visitInsn(ICONST_0); break; case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; case 4: mv.visitInsn(ICONST_4); break; case 5: mv.visitInsn(ICONST_5); break; default: mv.visitIntInsn(BIPUSH, value); } } @Override public void visitClass(Clazz clz) { throw new IllegalStateException("More specific method should be called"); } @Override public void visitMethod(Method m) { throw new IllegalStateException("More specific method should be called"); } @Override public void visitConcreteClass(ConcreteClass clz) { cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS); int ver = clz.ver(); int flags = clz.flags(); className = clz.intlName(); cw.visit((ver != 0) ? ver : defaultMajorVer, ACC_SUPER | ((flags != -1) ? flags : defaultClassAccFlags), className, /* signature */ clz.sig(), clz.parent().intlName(), Util.asStrings(clz.interfaces())); { // Default constructor: ()V mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, clz.parent().intlName(), "", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); mv = null; } for (Method m : clz.methods()) { m.visit(this); } cw.visitEnd(); } @Override public void visitInterface(Interface intf) { cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS); int ver = intf.ver(); int flags = intf.flags(); className = intf.intlName(); cw.visit( (ver != 0) ? ver : defaultMajorVer, ACC_ABSTRACT | ACC_INTERFACE | ((flags != -1) ? flags : defaultClassAccFlags), className, intf.sig(), "java/lang/Object", Util.asStrings(intf.parents())); for (Method m : intf.methods()) { m.visit(this); } cw.visitEnd(); } @Override public void visitConcreteMethod(ConcreteMethod m) { mv = cw.visitMethod( m.acc(), m.name(), m.desc(), m.sig(), m.getExceptions()); m.body().visit(this); mv = null; } @Override public void visitAbstractMethod(AbstractMethod m) { cw.visitMethod( ACC_ABSTRACT | m.acc(), m.name(), m.desc(), m.sig(), m.getExceptions()); } @Override public void visitDefaultMethod(DefaultMethod m) { mv = cw.visitMethod( m.acc(), m.name(), m.desc(), m.sig(), m.getExceptions()); m.body().visit(this); mv = null; } /* ====================================================================== */ @Override public void visitEmptyBody(EmptyBody aThis) { mv.visitCode(); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } @Override public void visitThrowExBody(ThrowExBody body) { mv.visitCode(); mv.visitTypeInsn(NEW, body.getExc().intlName()); mv.visitInsn(DUP); //mv.visitLdcInsn(body.getMsg()); //mv.visitMethodInsn(INVOKESPECIAL, body.getExc(), "", "(Ljava/lang/String;)V", false); mv.visitMethodInsn(INVOKESPECIAL, body.getExc().intlName(), "", "()V", false); mv.visitInsn(ATHROW); mv.visitMaxs(0, 0); mv.visitEnd(); } @Override public void visitReturnIntBody(ReturnIntBody body) { mv.visitCode(); //mv.visitIntInsn(BIPUSH, body.getValue()); pushIntConst(body.getValue()); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } @Override public void visitReturnNullBody(ReturnNullBody body) { mv.visitCode(); mv.visitInsn(ACONST_NULL); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } private void generateCall(CallMethod callSite, ExecutionMode invocationType) { switch (invocationType) { case DIRECT: generateDirectCall(callSite); break; case INVOKE_EXACT: generateMHInvokeCall(callSite, /* isExact = */ true); break; case INVOKE_GENERIC: generateMHInvokeCall(callSite, /* isExact = */ false); break; case INDY: generateIndyCall(callSite); break; default: throw new UnsupportedOperationException(invocationType.toString()); } } private void prepareParams(CallMethod callSite) { // Prepare receiver switch(callSite.invokeInsn()) { case SPECIAL: // Put receiver (this) on stack mv.visitVarInsn(ALOAD,0); break; case VIRTUAL: case INTERFACE: // Construct receiver if (callSite.receiverClass() != null) { String receiver = callSite.receiverClass().intlName(); // Construct new instance mv.visitTypeInsn(NEW, receiver); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, receiver, "", "()V", false); } else { // Use "this" mv.visitVarInsn(ALOAD, 0); } mv.visitVarInsn(ASTORE, 1); mv.visitVarInsn(ALOAD, 1); break; case STATIC: break; } // Push parameters on stack for (Param p : callSite.params()) { p.visit(this); } } private static Handle convertToHandle(CallMethod callSite) { return new Handle( /* tag */ callSite.invokeInsn().tag(), /* owner */ callSite.staticClass().intlName(), /* name */ callSite.methodName(), /* desc */ callSite.methodDesc(), /* interface */ callSite.isInterface()); } private Handle generateBootstrapMethod(CallMethod callSite) { String bootstrapName = "bootstrapMethod"; MethodType bootstrapType = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class); MethodVisitor bmv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, bootstrapName, bootstrapType.toMethodDescriptorString(), null, null); bmv.visitCode(); Handle mh = convertToHandle(callSite); String constCallSite = "java/lang/invoke/ConstantCallSite"; bmv.visitTypeInsn(NEW, constCallSite); bmv.visitInsn(DUP); bmv.visitLdcInsn(mh); bmv.visitMethodInsn(INVOKESPECIAL, constCallSite, "", "(Ljava/lang/invoke/MethodHandle;)V", false); bmv.visitInsn(ARETURN); bmv.visitMaxs(0,0); bmv.visitEnd(); return new Handle(H_INVOKESTATIC, className, bootstrapName, bootstrapType.toMethodDescriptorString()); } private static String mhCallSiteDesc(CallMethod callSite) { return (callSite.invokeInsn() != CallMethod.Invoke.STATIC) ? prependType(callSite.methodDesc(), callSite.staticClass().intlName()) : callSite.methodDesc(); // ignore receiver for static call } private void generateIndyCall(CallMethod callSite) { Handle bootstrap = generateBootstrapMethod(callSite); String callSiteDesc = mhCallSiteDesc(callSite); prepareParams(callSite); // Call method mv.visitInvokeDynamicInsn(callSite.methodName(), callSiteDesc, bootstrap); // Pop method result, if necessary if (callSite.popReturnValue()) { mv.visitInsn(POP); } } private void generateMHInvokeCall(CallMethod callSite, boolean isExact) { // Construct a method handle for a callee mv.visitLdcInsn(convertToHandle(callSite)); prepareParams(callSite); // Call method using MH + MethodHandle.invokeExact mv.visitMethodInsn( INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", isExact ? "invokeExact" : "invoke", mhCallSiteDesc(callSite), false); // Pop method result, if necessary if (callSite.popReturnValue()) { mv.visitInsn(POP); } } // Prepend type as a first parameter private static String prependType(String desc, String type) { return desc.replaceFirst("\\(", "(L"+type+";"); } private void generateDirectCall(CallMethod callSite) { prepareParams(callSite); // Call method mv.visitMethodInsn( callSite.invokeInsn().opcode(), callSite.staticClass().intlName(), callSite.methodName(), callSite.methodDesc(), callSite.isInterface()); // Pop method result, if necessary if (callSite.popReturnValue()) { mv.visitInsn(POP); } } @Override public void visitCallMethod(CallMethod callSite) { mv.visitCode(); generateCall(callSite, ExecutionMode.DIRECT); String typeName = callSite.returnType(); if (!callSite.popReturnValue()) { // Call produces some value & it isn't popped out of the stack // Need to return it switch (typeName) { // primitive types case "I" : case "B" : case "C" : case "S" : case "Z" : mv.visitInsn(IRETURN); break; case "L": mv.visitInsn(LRETURN); break; case "F": mv.visitInsn(FRETURN); break; case "D": mv.visitInsn(DRETURN); break; case "V": mv.visitInsn(RETURN); break; default: // reference type if ((typeName.startsWith("L") && typeName.endsWith(";")) || typeName.startsWith("[")) { mv.visitInsn(ARETURN); } else { throw new IllegalStateException(typeName); } } } else { // Stack is empty. Plain return is enough. mv.visitInsn(RETURN); } mv.visitMaxs(0,0); mv.visitEnd(); } @Override public void visitReturnNewInstanceBody(ReturnNewInstanceBody body) { String className = body.getType().intlName(); mv.visitCode(); mv.visitTypeInsn(NEW, className); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, className, "", "()V", false); mv.visitInsn(ARETURN); mv.visitMaxs(0,0); mv.visitEnd(); } /* ====================================================================== */ @Override public void visitTester(Tester tester) { // If: // cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS); // then: // java.lang.RuntimeException: java.lang.ClassNotFoundException: S // at jdk.internal.org.objectweb.asm.ClassWriter.getCommonSuperClass(ClassWriter.java:1588) // at jdk.internal.org.objectweb.asm.ClassWriter.getMergedType(ClassWriter.java:1559) // at jdk.internal.org.objectweb.asm.Frame.merge(Frame.java:1407) // at jdk.internal.org.objectweb.asm.Frame.merge(Frame.java:1308) // at jdk.internal.org.objectweb.asm.MethodWriter.visitMaxs(MethodWriter.java:1353) //mv.visitMaxs(t.getParams().length > 1 ? t.getParams().length+1 : 2, 2); cw = new ClassWriter(COMPUTE_MAXS); int testMajorVer = defaultMajorVer; // JSR 292 is available starting Java 7 (major version 51) if (invocationType == INVOKE_WITH_ARGS || invocationType == INVOKE_EXACT || invocationType == INVOKE_GENERIC) { testMajorVer = Math.max(defaultMajorVer, 51); } className = tester.intlName(); cw.visit(testMajorVer, ACC_PUBLIC | ACC_SUPER, className, null, "java/lang/Object", null); { // Test. mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); mv = null; } { // public static Test.test()V mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "()V", null, null); try { // Generate result handling t = tester; try { tester.getResult().visit(this); } finally { t = null; } } finally { mv = null; } } cw.visitEnd(); } /* ====================================================================== */ @Override public void visitResultInt(IntResult res) { mv.visitCode(); generateCall(t.getCall(), invocationType); mv.visitIntInsn(BIPUSH, res.getExpected()); mv.visitMethodInsn(INVOKESTATIC, Util.getInternalName(TestUtils.class), "assertEquals", "(II)V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } /** * Pseudo code: * * { * try { * I i = new C(); i.m(...); // C.m(); if m is static * Assert.fail(); * } catch (<exception> e) { * Assert.assertEquals(<message>,e.getMessage()); * } catch (Throwable e) { * throw new RuntimeException("...", e); * } * } * */ @Override public void visitResultThrowExc(ThrowExResult res) { mv.visitCode(); Label lblBegin = new Label(); Label lblBootstrapMethodError = new Label(); Label lblNoBME = new Label(); if (invocationType == INDY) { mv.visitTryCatchBlock(lblBegin, lblNoBME, lblBootstrapMethodError, "java/lang/BootstrapMethodError"); } Label lblExpected = new Label(); mv.visitTryCatchBlock(lblBegin, lblExpected, lblExpected, res.getExc().intlName()); Label lblThrowable = new Label(); mv.visitTryCatchBlock(lblBegin, lblExpected, lblThrowable, "java/lang/Throwable"); mv.visitLabel(lblBegin); generateCall(t.getCall(), invocationType); if (Util.isNonVoid(t.getCall().returnType())) { mv.visitInsn(POP); } mv.visitLabel(lblNoBME); // throw new TestFailure("No exception was thrown") mv.visitTypeInsn(NEW, "nsk/share/TestFailure"); mv.visitInsn(DUP); mv.visitLdcInsn("No exception was thrown"); mv.visitMethodInsn(INVOKESPECIAL, "nsk/share/TestFailure", "", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); // Unwrap exception during call site resolution from BootstrapMethodError if (invocationType == INDY) { // } catch (BootstrapMethodError e) { // throw e.getCause(); // } mv.visitLabel(lblBootstrapMethodError); mv.visitFrame(F_SAME1, 0, null, 1, new Object[] {"java/lang/BootstrapMethodError"}); mv.visitVarInsn(ASTORE, 1); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/BootstrapMethodError", "getCause", "()Ljava/lang/Throwable;", false); Label lblIsNull = new Label(); mv.visitJumpInsn(IFNULL, lblIsNull); // e.getCause() != null mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/BootstrapMethodError", "getCause", "()Ljava/lang/Throwable;", false); mv.visitInsn(ATHROW); // e.getCause() == null mv.visitLabel(lblIsNull); mv.visitFrame(F_APPEND, 2, new Object[] {TOP, "java/lang/BootstrapMethodError"}, 0, null); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/BootstrapMethodError", "getCause", "()Ljava/lang/Throwable;", false); mv.visitInsn(ATHROW); } // } catch ( e) { // //if != null // Assert.assertEquals(,e.getMessage()); // } mv.visitLabel(lblExpected); mv.visitFrame(F_FULL, 0, new Object[] {}, 1, new Object[] { res.getExc().intlName() }); mv.visitVarInsn(ASTORE, 1); // Exception class comparison, if exact match is requested if (res.isExact()) { mv.visitVarInsn(ALOAD, 1); mv.visitLdcInsn(Type.getType("L"+res.getExc().intlName()+";")); mv.visitMethodInsn(INVOKESTATIC, Util.getInternalName(TestUtils.class), "assertExactClass", "(Ljava/lang/Object;Ljava/lang/Class;)V", false); } // Compare exception's message, if needed if (res.getMessage() != null) { mv.visitVarInsn(ALOAD, 1); mv.visitLdcInsn(res.getMessage()); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESTATIC, Util.getInternalName(TestUtils.class), "assertEquals", "(Ljava/lang/String;Ljava/lang/String;)V", false); } mv.visitInsn(RETURN); // } catch (Throwable e) { // throw new RuntimeException("Expected exception ", e); // } mv.visitLabel(lblThrowable); mv.visitFrame(F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); mv.visitVarInsn(ASTORE, 1); // e.printStackTrace(); //mv.visitVarInsn(ALOAD, 1); //mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false); // String msg = String.format("Expected exception J, got: %s: %s", // e.getClass(), e.getMessage()); // throw new RuntimeException(msg, e); mv.visitTypeInsn(NEW, Util.getInternalName(TestFailure.class)); mv.visitInsn(DUP); mv.visitLdcInsn("Expected exception " + res.getExc().name() + ", got: %s: %s"); mv.visitInsn(ICONST_2); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); mv.visitInsn(DUP); mv.visitInsn(ICONST_0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); mv.visitInsn(AASTORE); mv.visitInsn(DUP); mv.visitInsn(ICONST_1); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;", false); mv.visitInsn(AASTORE); mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, Util.getInternalName(TestFailure.class), "", "(Ljava/lang/String;Ljava/lang/Throwable;)V", false); mv.visitInsn(ATHROW); // end of lblThrowable mv.visitMaxs(0, 0); mv.visitEnd(); } @Override public void visitResultIgnore() { mv.visitCode(); generateCall(t.getCall(), invocationType); if (Util.isNonVoid(t.getCall().returnType())) { mv.visitInsn(POP); } mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } /* ====================================================================== */ @Override public void visitParamInt(IntParam i) { pushIntConst(i.value()); } @Override public void visitParamLong(LongParam l) { long value = l.value(); if (value == 0L) { mv.visitInsn(LCONST_0); } else { mv.visitLdcInsn(new Long(value)); } } @Override public void visitParamFloat(FloatParam f) { float value = f.value(); if (value == 0.0f) { mv.visitInsn(FCONST_0); } else if (value == 1.0f) { mv.visitInsn(FCONST_1); } else if (value == 2.0f) { mv.visitInsn(FCONST_2); } else { mv.visitLdcInsn(new Float(value)); } } @Override public void visitParamDouble(DoubleParam d) { double value = d.value(); if (value == 0.0d) { mv.visitInsn(DCONST_0); } else if (value == 1.0d) { mv.visitInsn(DCONST_1); } else { mv.visitLdcInsn(new Double(value)); } } @Override public void visitParamNewInstance(NewInstanceParam param) { String className = param.clazz().intlName(); mv.visitTypeInsn(NEW, className); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, className, "", "()V", false); } @Override public void visitParamNull() { mv.visitInsn(ACONST_NULL); } @Override public void visitParamString(StringParam str) { mv.visitLdcInsn(str.value()); } }