diff --git a/src/java.base/share/classes/jdk/internal/classfile/Instruction.java b/src/java.base/share/classes/jdk/internal/classfile/Instruction.java index 7b0643c4533..9a1a81d548e 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/Instruction.java +++ b/src/java.base/share/classes/jdk/internal/classfile/Instruction.java @@ -31,6 +31,7 @@ import jdk.internal.classfile.instruction.ArrayStoreInstruction; import jdk.internal.classfile.instruction.BranchInstruction; import jdk.internal.classfile.instruction.ConstantInstruction; import jdk.internal.classfile.instruction.ConvertInstruction; +import jdk.internal.classfile.instruction.DiscontinuedInstruction; import jdk.internal.classfile.instruction.FieldInstruction; import jdk.internal.classfile.instruction.IncrementInstruction; import jdk.internal.classfile.instruction.InvokeDynamicInstruction; @@ -56,9 +57,9 @@ import jdk.internal.classfile.instruction.TypeCheckInstruction; */ public sealed interface Instruction extends CodeElement permits ArrayLoadInstruction, ArrayStoreInstruction, BranchInstruction, - ConstantInstruction, ConvertInstruction, FieldInstruction, - InvokeDynamicInstruction, InvokeInstruction, LoadInstruction, - StoreInstruction, IncrementInstruction, + ConstantInstruction, ConvertInstruction, DiscontinuedInstruction, + FieldInstruction, InvokeDynamicInstruction, InvokeInstruction, + LoadInstruction, StoreInstruction, IncrementInstruction, LookupSwitchInstruction, MonitorInstruction, NewMultiArrayInstruction, NewObjectInstruction, NewPrimitiveArrayInstruction, NewReferenceArrayInstruction, NopInstruction, OperatorInstruction, ReturnInstruction, diff --git a/src/java.base/share/classes/jdk/internal/classfile/Opcode.java b/src/java.base/share/classes/jdk/internal/classfile/Opcode.java index 5aaf8778cab..6779e55448b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/Opcode.java +++ b/src/java.base/share/classes/jdk/internal/classfile/Opcode.java @@ -204,8 +204,8 @@ public enum Opcode { IF_ACMPEQ(Classfile.IF_ACMPEQ, 3, Kind.BRANCH, TypeKind.ReferenceType), IF_ACMPNE(Classfile.IF_ACMPNE, 3, Kind.BRANCH, TypeKind.ReferenceType), GOTO(Classfile.GOTO, 3, Kind.BRANCH, TypeKind.VoidType), - JSR(Classfile.JSR, 3, Kind.UNSUPPORTED), - RET(Classfile.RET, 2, Kind.UNSUPPORTED), + JSR(Classfile.JSR, 3, Kind.DISCONTINUED_JSR), + RET(Classfile.RET, 2, Kind.DISCONTINUED_RET), TABLESWITCH(Classfile.TABLESWITCH, -1, Kind.TABLE_SWITCH), LOOKUPSWITCH(Classfile.LOOKUPSWITCH, -1, Kind.LOOKUP_SWITCH), IRETURN(Classfile.IRETURN, 1, Kind.RETURN, TypeKind.IntType), @@ -236,7 +236,7 @@ public enum Opcode { IFNULL(Classfile.IFNULL, 3, Kind.BRANCH, TypeKind.ReferenceType), IFNONNULL(Classfile.IFNONNULL, 3, Kind.BRANCH, TypeKind.IntType), GOTO_W(Classfile.GOTO_W, 5, Kind.BRANCH, TypeKind.VoidType), - JSR_W(Classfile.JSR_W, 5, Kind.UNSUPPORTED), + JSR_W(Classfile.JSR_W, 5, Kind.DISCONTINUED_JSR), ILOAD_W((Classfile.WIDE << 8) | Classfile.ILOAD, 4, Kind.LOAD, TypeKind.IntType, -1), LLOAD_W((Classfile.WIDE << 8) | Classfile.LLOAD, 4, Kind.LOAD, TypeKind.LongType, -1), FLOAD_W((Classfile.WIDE << 8) | Classfile.FLOAD, 4, Kind.LOAD, TypeKind.FloatType, -1), @@ -247,7 +247,7 @@ public enum Opcode { FSTORE_W((Classfile.WIDE << 8) | Classfile.FSTORE, 4, Kind.STORE, TypeKind.FloatType, -1), DSTORE_W((Classfile.WIDE << 8) | Classfile.DSTORE, 4, Kind.STORE, TypeKind.DoubleType, -1), ASTORE_W((Classfile.WIDE << 8) | Classfile.ASTORE, 4, Kind.STORE, TypeKind.ReferenceType, -1), - RET_W((Classfile.WIDE << 8) | Classfile.RET, 4, Kind.UNSUPPORTED), + RET_W((Classfile.WIDE << 8) | Classfile.RET, 4, Kind.DISCONTINUED_RET), IINC_W((Classfile.WIDE << 8) | Classfile.IINC, 6, Kind.INCREMENT, TypeKind.IntType, -1); /** @@ -258,7 +258,7 @@ public enum Opcode { FIELD_ACCESS, INVOKE, INVOKE_DYNAMIC, NEW_OBJECT, NEW_PRIMITIVE_ARRAY, NEW_REF_ARRAY, NEW_MULTI_ARRAY, TYPE_CHECK, ARRAY_LOAD, ARRAY_STORE, STACK, CONVERT, OPERATOR, CONSTANT, - MONITOR, NOP, UNSUPPORTED; + MONITOR, NOP, DISCONTINUED_JSR, DISCONTINUED_RET; } private final int bytecode; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java index a0879be3efb..10dd19c4b16 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java @@ -42,6 +42,7 @@ import jdk.internal.classfile.instruction.ArrayStoreInstruction; import jdk.internal.classfile.instruction.BranchInstruction; import jdk.internal.classfile.instruction.ConstantInstruction; import jdk.internal.classfile.instruction.ConvertInstruction; +import jdk.internal.classfile.instruction.DiscontinuedInstruction; import jdk.internal.classfile.instruction.FieldInstruction; import jdk.internal.classfile.instruction.IncrementInstruction; import jdk.internal.classfile.instruction.InvokeDynamicInstruction; @@ -89,7 +90,8 @@ public abstract sealed class AbstractInstruction FMT_TableSwitch = "TableSwitch[OP=%s]", FMT_Throw = "Throw[OP=%s]", FMT_TypeCheck = "TypeCheck[OP=%s, type=%s]", - FMT_Unbound = "%s[op=%s]"; + FMT_Unbound = "%s[op=%s]", + FMT_Discontinued = "Discontinued[OP=%s]"; final Opcode op; final int size; @@ -705,6 +707,59 @@ public abstract sealed class AbstractInstruction } + public static final class BoundJsrInstruction + extends BoundInstruction implements DiscontinuedInstruction.JsrInstruction { + + public BoundJsrInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public Label target() { + return offsetToLabel(branchByteOffset()); + } + + public int branchByteOffset() { + return size == 3 + ? code.classReader.readS2(pos + 1) + : code.classReader.readInt(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(opcode(), target()); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + + } + + public static final class BoundRetInstruction + extends BoundInstruction implements DiscontinuedInstruction.RetInstruction { + + public BoundRetInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + + @Override + public int slot() { + return switch (size) { + case 2 -> code.classReader.readU1(pos + 1); + case 4 -> code.classReader.readU2(pos + 2); + default -> throw new IllegalArgumentException("Unexpected op size: " + op.sizeIfFixed() + " -- " + op); + }; + } + + } + public static abstract sealed class UnboundInstruction extends AbstractInstruction { UnboundInstruction(Opcode op) { @@ -744,7 +799,7 @@ public abstract sealed class AbstractInstruction @Override public void writeTo(DirectCodeBuilder writer) { - writer.writeLoad(op, slot); + writer.writeLocalVar(op, slot); } @Override @@ -775,7 +830,7 @@ public abstract sealed class AbstractInstruction @Override public void writeTo(DirectCodeBuilder writer) { - writer.writeStore(op, slot); + writer.writeLocalVar(op, slot); } @Override @@ -1339,4 +1394,54 @@ public abstract sealed class AbstractInstruction } } + + public static final class UnboundJsrInstruction + extends UnboundInstruction implements DiscontinuedInstruction.JsrInstruction { + final Label target; + + public UnboundJsrInstruction(Opcode op, Label target) { + super(op); + this.target = target; + } + + @Override + public Label target() { + return target; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(op, target); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + } + + public static final class UnboundRetInstruction + extends UnboundInstruction implements DiscontinuedInstruction.RetInstruction { + final int slot; + + public UnboundRetInstruction(Opcode op, int slot) { + super(op); + this.slot = slot; + } + + @Override + public int slot() { + return slot; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLocalVar(op, slot); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java index cc1d132d6b0..8873d965402 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java @@ -40,17 +40,24 @@ public final class BufWriterImpl implements BufWriter { private final ConstantPoolBuilder constantPool; private LabelContext labelContext; - private ClassEntry thisClass; + private final ClassEntry thisClass; + private final int majorVersion; byte[] elems; int offset = 0; public BufWriterImpl(ConstantPoolBuilder constantPool) { - this(constantPool, 64); + this(constantPool, 64, null, 0); } public BufWriterImpl(ConstantPoolBuilder constantPool, int initialSize) { + this(constantPool, initialSize, null, 0); + } + + public BufWriterImpl(ConstantPoolBuilder constantPool, int initialSize, ClassEntry thisClass, int majorVersion) { this.constantPool = constantPool; elems = new byte[initialSize]; + this.thisClass = thisClass; + this.majorVersion = majorVersion; } @Override @@ -74,8 +81,8 @@ public final class BufWriterImpl implements BufWriter { return thisClass; } - public void setThisClass(ClassEntry thisClass) { - this.thisClass = thisClass; + public int getMajorVersion() { + return majorVersion; } @Override diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java index 795c7d0816b..4c1e88481c4 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java @@ -851,6 +851,10 @@ public final class ClassPrinterImpl { "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) .map(com::labelToBci), si.cases().stream() .map(sc -> com.labelToBci(sc.target()))))); + case DiscontinuedInstruction.JsrInstruction jsr -> in.with(leaf( + "target", com.labelToBci(jsr.target()))); + case DiscontinuedInstruction.RetInstruction ret -> in.with(leaf( + "slot", ret.slot())); default -> {} } bci += ins.sizeInBytes(); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java index cbab475f3a1..2028098f16b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java @@ -242,8 +242,21 @@ public final class CodeImpl private void inflateJumpTargets() { Optional a = findAttribute(Attributes.STACK_MAP_TABLE); - if (a.isEmpty()) + if (a.isEmpty()) { + if (classReader.readU2(6) <= Classfile.JAVA_6_VERSION) { + //fallback to jump targets inflation without StackMapTableAttribute + for (int pos=codeStart; pos br.target(); + case DiscontinuedInstruction.JsrInstruction jsr -> jsr.target(); + default -> {} + } + pos += i.sizeInBytes(); + } + } return; + } @SuppressWarnings("unchecked") int stackMapPos = ((BoundAttribute) a.get()).payloadStart; @@ -442,7 +455,7 @@ public final class CodeImpl case DSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.DSTORE_W, this, pos); case ASTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ASTORE_W, this, pos); case IINC -> new AbstractInstruction.BoundIncrementInstruction(Opcode.IINC_W, this, pos); - case RET -> throw new UnsupportedOperationException("RET_W instruction not supported"); + case RET -> new AbstractInstruction.BoundRetInstruction(Opcode.RET_W, this, pos); default -> throw new UnsupportedOperationException("unknown wide instruction: " + bclow); }; } @@ -452,9 +465,9 @@ public final class CodeImpl case IFNONNULL -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNONNULL, CodeImpl.this, pos); case GOTO_W -> new AbstractInstruction.BoundBranchInstruction(Opcode.GOTO_W, CodeImpl.this, pos); - case JSR -> throw new UnsupportedOperationException("JSR instruction not supported"); - case RET -> throw new UnsupportedOperationException("RET instruction not supported"); - case JSR_W -> throw new UnsupportedOperationException("JSR_W instruction not supported"); + case JSR -> new AbstractInstruction.BoundJsrInstruction(Opcode.JSR, CodeImpl.this, pos); + case RET -> new AbstractInstruction.BoundRetInstruction(Opcode.RET, this, pos); + case JSR_W -> new AbstractInstruction.BoundJsrInstruction(Opcode.JSR_W, CodeImpl.this, pos); default -> { Instruction instr = SINGLETON_INSTRUCTIONS[bc]; if (instr == null) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java index f53cab278d3..e2f27afa786 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java @@ -167,8 +167,7 @@ public final class DirectClassBuilder // We maintain two writers, and then we join them at the end int size = sizeHint == 0 ? 256 : sizeHint; BufWriter head = new BufWriterImpl(constantPool, size); - BufWriterImpl tail = new BufWriterImpl(constantPool, size); - tail.setThisClass(thisClassEntry); + BufWriterImpl tail = new BufWriterImpl(constantPool, size, thisClassEntry, majorVersion); // The tail consists of fields and methods, and attributes // This should trigger all the CP/BSM mutation diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java index 8d282347f63..f8c26d77f13 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java @@ -66,6 +66,8 @@ import static jdk.internal.classfile.Opcode.GOTO; import static jdk.internal.classfile.Opcode.GOTO_W; import static jdk.internal.classfile.Opcode.IINC; import static jdk.internal.classfile.Opcode.IINC_W; +import static jdk.internal.classfile.Opcode.JSR; +import static jdk.internal.classfile.Opcode.JSR_W; import static jdk.internal.classfile.Opcode.LDC2_W; import static jdk.internal.classfile.Opcode.LDC_W; @@ -73,12 +75,12 @@ public final class DirectCodeBuilder extends AbstractDirectBuilder implements TerminalCodeBuilder, LabelContext { private final List characterRanges = new ArrayList<>(); - private final List handlers = new ArrayList<>(); + final List handlers = new ArrayList<>(); private final List localVariables = new ArrayList<>(); private final List localVariableTypes = new ArrayList<>(); private final boolean transformFwdJumps, transformBackJumps; private final Label startLabel, endLabel; - private final MethodInfo methodInfo; + final MethodInfo methodInfo; final BufWriter bytecodesBufWriter; private CodeAttribute mruParent; private int[] mruParentTable; @@ -314,7 +316,9 @@ public final class DirectCodeBuilder boolean canReuseStackmaps = codeAndExceptionsMatch(codeLength); if (!constantPool.options().generateStackmaps) { - maxStack = maxLocals = 255; + StackCounter cntr = StackCounter.of(DirectCodeBuilder.this, buf); + maxStack = cntr.maxStack(); + maxLocals = cntr.maxLocals(); stackMapAttr = null; } else if (canReuseStackmaps) { @@ -322,21 +326,33 @@ public final class DirectCodeBuilder maxStack = original.maxStack(); stackMapAttr = original.findAttribute(Attributes.STACK_MAP_TABLE).orElse(null); } - else { - //new instance of generator immediately calculates maxStack, maxLocals, all frames, - // patches dead bytecode blocks and removes them from exception table - StackMapGenerator gen = new StackMapGenerator(DirectCodeBuilder.this, - buf.thisClass().asSymbol(), - methodInfo.methodName().stringValue(), - MethodTypeDesc.ofDescriptor(methodInfo.methodType().stringValue()), - (methodInfo.methodFlags() & Classfile.ACC_STATIC) != 0, - bytecodesBufWriter.asByteBuffer().slice(0, codeLength), - constantPool, - handlers); - maxStack = gen.maxStack(); - maxLocals = gen.maxLocals(); - stackMapAttr = gen.stackMapTableAttribute(); + else if (buf.getMajorVersion() >= Classfile.JAVA_6_VERSION) { + try { + //new instance of generator immediately calculates maxStack, maxLocals, all frames, + // patches dead bytecode blocks and removes them from exception table + StackMapGenerator gen = StackMapGenerator.of(DirectCodeBuilder.this, buf); + maxStack = gen.maxStack(); + maxLocals = gen.maxLocals(); + stackMapAttr = gen.stackMapTableAttribute(); + } catch (Exception e) { + if (buf.getMajorVersion() == Classfile.JAVA_6_VERSION) { + //failover following JVMS-4.10 + StackCounter cntr = StackCounter.of(DirectCodeBuilder.this, buf); + maxStack = cntr.maxStack(); + maxLocals = cntr.maxLocals(); + stackMapAttr = null; + } else { + throw e; + } + } } + else { + StackCounter cntr = StackCounter.of(DirectCodeBuilder.this, buf); + maxStack = cntr.maxStack(); + maxLocals = cntr.maxLocals(); + stackMapAttr = null; + } + attributes.withAttribute(stackMapAttr); buf.writeU2(maxStack); @@ -449,17 +465,7 @@ public final class DirectCodeBuilder bytecodesBufWriter.writeU1(opcode.bytecode() & 0xFF); } - public void writeLoad(Opcode opcode, int localVar) { - writeBytecode(opcode); - switch (opcode.sizeIfFixed()) { - case 1 -> { } - case 2 -> bytecodesBufWriter.writeU1(localVar); - case 4 -> bytecodesBufWriter.writeU2(localVar); - default -> throw new IllegalArgumentException("Unexpected instruction size: " + opcode); - } - } - - public void writeStore(Opcode opcode, int localVar) { + public void writeLocalVar(Opcode opcode, int localVar) { writeBytecode(opcode); switch (opcode.sizeIfFixed()) { case 1 -> { } @@ -494,6 +500,9 @@ public final class DirectCodeBuilder if (op == GOTO) { writeBytecode(GOTO_W); writeLabelOffset(4, instructionPc, target); + } else if (op == JSR) { + writeBytecode(JSR_W); + writeLabelOffset(4, instructionPc, target); } else { writeBytecode(BytecodeHelpers.reverseBranchOpcode(op)); Label bypassJump = newLabel(); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java index ec517a95a18..d2f214ef822 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java @@ -116,6 +116,18 @@ public final class SplitConstantPool implements ConstantPoolBuilder { this.myBsmEntries = new BootstrapMethodEntryImpl[8]; } + //clone constructor for internal purposes + SplitConstantPool(SplitConstantPool cloneFrom, Options options) { + this.options = options; + this.parent = cloneFrom.parent; + this.parentSize = cloneFrom.parentSize; + this.parentBsmSize = cloneFrom.parentBsmSize; + this.size = cloneFrom.size; + this.bsmSize = cloneFrom.bsmSize; + this.myEntries = Arrays.copyOf(cloneFrom.myEntries, cloneFrom.myEntries.length); + this.myBsmEntries = Arrays.copyOf(cloneFrom.myBsmEntries, cloneFrom.myBsmEntries.length); + } + @Override public int entryCount() { return size; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java new file mode 100644 index 00000000000..cf5a5fb9b0c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -0,0 +1,380 @@ +/* + * 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. + * + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.nio.ByteBuffer; +import java.util.BitSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import static jdk.internal.classfile.Classfile.*; + + +public final class StackCounter { + + static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { + return new StackCounter( + dcb, + buf.thisClass().asSymbol(), + dcb.methodInfo.methodName().stringValue(), + MethodTypeDesc.ofDescriptor(dcb.methodInfo.methodType().stringValue()), + (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, + dcb.bytecodesBufWriter.asByteBuffer().slice(0, dcb.bytecodesBufWriter.size()), + dcb.constantPool, + dcb.handlers); + } + + private int stack, maxStack, maxLocals, rets; + + private final RawBytecodeHelper bcs; + private final String methodName; + private final MethodTypeDesc methodDesc; + private final SplitConstantPool cp; + private final LinkedHashMap map; + private final BitSet visited; + + private void jump(int targetBci) { + if (!visited.get(targetBci)) { + map.put(targetBci, stack); + } + } + + private void addStackSlot(int delta) { + stack += delta; + if (stack > maxStack) maxStack = stack; + } + + private void ensureLocalSlot(int index) { + if (index >= maxLocals) maxLocals = index + 1; + } + + private boolean next() { + var it = map.entrySet().iterator(); + while (it.hasNext()) { + var en = it.next(); + it.remove(); + if (!visited.get(en.getKey())) { + bcs.nextBci = en.getKey(); + stack = en.getValue(); + return true; + } + } + bcs.nextBci = bcs.endBci; + return false; + } + + public StackCounter(LabelContext labelContext, + ClassDesc thisClass, + String methodName, + MethodTypeDesc methodDesc, + boolean isStatic, + ByteBuffer bytecode, + SplitConstantPool cp, + List handlers) { + this.methodName = methodName; + this.methodDesc = methodDesc; + this.cp = cp; + map = new LinkedHashMap<>(); + maxStack = stack = rets = 0; + for (var h : handlers) map.put(labelContext.labelToBci(h.handler), 1); + maxLocals = isStatic ? 0 : 1; + for (var cd : methodDesc.parameterList()) { + maxLocals += TypeKind.from(cd).slotSize(); + } + bcs = new RawBytecodeHelper(bytecode); + visited = new BitSet(bcs.endBci); + map.put(0, 0); + while (next()) { + while (!bcs.isLastBytecode()) { + bcs.rawNext(); + int opcode = bcs.rawCode; + int bci = bcs.bci; + visited.set(bci); + switch (opcode) { + case NOP, LALOAD, DALOAD, SWAP, INEG, ARRAYLENGTH, INSTANCEOF, LNEG, FNEG, DNEG, I2F, L2D, F2I, D2L, I2B, I2C, I2S, + NEWARRAY, CHECKCAST, ANEWARRAY -> {} + case RETURN -> + next(); + case ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH, + FCONST_0, FCONST_1, FCONST_2, DUP, DUP_X1, DUP_X2, I2L, I2D, F2L, F2D, NEW -> + addStackSlot(+1); + case LCONST_0, LCONST_1, DCONST_0, DCONST_1, DUP2, DUP2_X1, DUP2_X2 -> + addStackSlot(+2); + case POP, MONITORENTER, MONITOREXIT, IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND, + LSHL, LSHR, LUSHR, FADD, FSUB, FMUL, FDIV, FREM, L2I, L2F, D2F, FCMPL, FCMPG, D2I -> + addStackSlot(-1); + case POP2, LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR, DADD, DSUB, DMUL, DDIV, DREM -> + addStackSlot(-2); + case IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE, LCMP, DCMPL, DCMPG -> + addStackSlot(-3); + case LASTORE, DASTORE -> + addStackSlot(-4); + case LDC -> + processLdc(bcs.getIndexU1()); + case LDC_W, LDC2_W -> + processLdc(bcs.getIndexU2()); + case ILOAD, FLOAD, ALOAD -> { + ensureLocalSlot(bcs.getIndex()); + addStackSlot(+1); + } + case LLOAD, DLOAD -> { + ensureLocalSlot(bcs.getIndex() + 1); + addStackSlot(+2); + } + case ILOAD_0, FLOAD_0, ALOAD_0 -> { + ensureLocalSlot(0); + addStackSlot(+1); + } + case ILOAD_1, FLOAD_1, ALOAD_1 -> { + ensureLocalSlot(1); + addStackSlot(+1); + } + case ILOAD_2, FLOAD_2, ALOAD_2 -> { + ensureLocalSlot(2); + addStackSlot(+1); + } + case ILOAD_3, FLOAD_3, ALOAD_3 -> { + ensureLocalSlot(3); + addStackSlot(+1); + } + case LLOAD_0, DLOAD_0 -> { + ensureLocalSlot(1); + addStackSlot(+2); + } + case LLOAD_1, DLOAD_1 -> { + ensureLocalSlot(2); + addStackSlot(+2); + } + case LLOAD_2, DLOAD_2 -> { + ensureLocalSlot(3); + addStackSlot(+2); + } + case LLOAD_3, DLOAD_3 -> { + ensureLocalSlot(4); + addStackSlot(+2); + } + case IALOAD, BALOAD, CALOAD, SALOAD, FALOAD, AALOAD -> { + addStackSlot(-1); + } + case ISTORE, FSTORE, ASTORE -> { + ensureLocalSlot(bcs.getIndex()); + addStackSlot(-1); + } + case LSTORE, DSTORE -> { + ensureLocalSlot(bcs.getIndex() + 1); + addStackSlot(-2); + } + case ISTORE_0, FSTORE_0, ASTORE_0 -> { + ensureLocalSlot(0); + addStackSlot(-1); + } + case ISTORE_1, FSTORE_1, ASTORE_1 -> { + ensureLocalSlot(1); + addStackSlot(-1); + } + case ISTORE_2, FSTORE_2, ASTORE_2 -> { + ensureLocalSlot(2); + addStackSlot(-1); + } + case ISTORE_3, FSTORE_3, ASTORE_3 -> { + ensureLocalSlot(3); + addStackSlot(-1); + } + case LSTORE_0, DSTORE_0 -> { + ensureLocalSlot(1); + addStackSlot(-2); + } + case LSTORE_1, DSTORE_1 -> { + ensureLocalSlot(2); + addStackSlot(-2); + } + case LSTORE_2, DSTORE_2 -> { + ensureLocalSlot(3); + addStackSlot(-2); + } + case LSTORE_3, DSTORE_3 -> { + ensureLocalSlot(4); + addStackSlot(-2); + } + case IINC -> + ensureLocalSlot(bcs.getIndex()); + case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + addStackSlot(-2); + jump(bcs.dest()); + } + case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + addStackSlot(-1); + jump(bcs.dest()); + } + case GOTO -> { + jump(bcs.dest()); + next(); + } + case GOTO_W -> { + jump(bcs.destW()); + next(); + } + case TABLESWITCH, LOOKUPSWITCH -> { + int alignedBci = RawBytecodeHelper.align(bci + 1); + int defaultOfset = bcs.getInt(alignedBci); + int keys, delta; + addStackSlot(-1); + if (bcs.rawCode == TABLESWITCH) { + int low = bcs.getInt(alignedBci + 4); + int high = bcs.getInt(alignedBci + 2 * 4); + if (low > high) { + error("low must be less than or equal to high in tableswitch"); + } + keys = high - low + 1; + if (keys < 0) { + error("too many keys in tableswitch"); + } + delta = 1; + } else { + keys = bcs.getInt(alignedBci + 4); + if (keys < 0) { + error("number of keys in lookupswitch less than 0"); + } + delta = 2; + for (int i = 0; i < (keys - 1); i++) { + int this_key = bcs.getInt(alignedBci + (2 + 2 * i) * 4); + int next_key = bcs.getInt(alignedBci + (2 + 2 * i + 2) * 4); + if (this_key >= next_key) { + error("Bad lookupswitch instruction"); + } + } + } + int target = bci + defaultOfset; + jump(target); + for (int i = 0; i < keys; i++) { + alignedBci = RawBytecodeHelper.align(bcs.bci + 1); + target = bci + bcs.getInt(alignedBci + (3 + i * delta) * 4); + jump(target); + } + next(); + } + case LRETURN, DRETURN -> { + addStackSlot(-2); + next(); + } + case IRETURN, FRETURN, ARETURN, ATHROW -> { + addStackSlot(-1); + next(); + } + case GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD -> { + var tk = TypeKind.fromDescriptor(((MemberRefEntry)cp.entryByIndex(bcs.getIndexU2())).nameAndType().type().stringValue()); + switch (bcs.rawCode) { + case GETSTATIC -> + addStackSlot(tk.slotSize()); + case PUTSTATIC -> + addStackSlot(-tk.slotSize()); + case GETFIELD -> + addStackSlot(tk.slotSize() - 1); + case PUTFIELD -> + addStackSlot(-tk.slotSize() - 1); + default -> throw new AssertionError("Should not reach here"); + } + } + case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> { + var cpe = cp.entryByIndex(bcs.getIndexU2()); + var nameAndType = opcode == INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); + var mDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); + for (var arg : mDesc.parameterList()) { + addStackSlot(-TypeKind.from(arg).slotSize()); + } + if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { + addStackSlot(-1); + } + addStackSlot(TypeKind.from(mDesc.returnType()).slotSize()); + } + case MULTIANEWARRAY -> + addStackSlot (1 - bcs.getU1(bcs.bci + 3)); + case JSR -> { + addStackSlot(+1); + jump(bcs.dest()); //here we lost track of the exact stack size after return from subroutine + addStackSlot(-1); + } + case JSR_W -> { + addStackSlot(+1); + jump(bcs.destW()); //here we lost track of the exact stack size after return from subroutine + addStackSlot(-1); + } + case RET -> { + ensureLocalSlot(bcs.getIndex()); + rets++; //subroutines must be counted for later maxStack correction + next(); + } + default -> + error(String.format("Bad instruction: %02x", opcode)); + } + } + } + //correction of maxStack when subroutines are present by calculation of upper bounds + //the worst scenario is that all subroutines are chained and each subroutine also requires maxStack for its own code + maxStack += rets * maxStack; + } + + /** + * Calculated maximum number of the locals required + * @return maximum number of the locals required + */ + public int maxLocals() { + return maxLocals; + } + + /** + * Calculated maximum stack size required + * @return maximum stack size required + */ + public int maxStack() { + return maxStack; + } + + private void processLdc(int index) { + switch (cp.entryByIndex(index).tag()) { + case TAG_UTF8, TAG_STRING, TAG_CLASS, TAG_INTEGER, TAG_FLOAT, TAG_METHODHANDLE, TAG_METHODTYPE -> + addStackSlot(+1); + case TAG_DOUBLE, TAG_LONG -> + addStackSlot(+2); + case TAG_CONSTANTDYNAMIC -> + addStackSlot(((ConstantDynamicEntry)cp.entryByIndex(index)).typeKind().slotSize()); + default -> + error("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag())); + } + } + + private void error(String msg) { + throw new IllegalArgumentException("%s at bytecode offset %d of method %s(%s)".formatted( + msg, + bcs.bci, + methodName, + methodDesc.displayDescriptor())); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java index 9466ef069e5..921e326ac4d 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java @@ -140,21 +140,22 @@ import jdk.internal.classfile.attribute.CodeAttribute; *
  • It works with only minimal mandatory stack map frames. *
  • It does not spend time on any non-essential verifications. * - *

    - * In case of an exception during the Generator loop there is just minimal information available in the exception message. - *

    - * To determine root cause of the exception it is recommended to enable debug logging of the Generator in one of the two modes - * using following java.lang.System properties:

    - *
    -Djdk.internal.classfile.impl.StackMapGenerator.DEBUG=true - *
    Activates debug logging with basic information + generated stack map frames in case of success. - * It also re-runs with enabled full trace logging in case of an error or exception. - *
    -Djdk.internal.classfile.impl.StackMapGenerator.TRACE=true - *
    Activates full detailed tracing of the generator process for all invocations. - *
    */ public final class StackMapGenerator { + static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) { + return new StackMapGenerator( + dcb, + buf.thisClass().asSymbol(), + dcb.methodInfo.methodName().stringValue(), + MethodTypeDesc.ofDescriptor(dcb.methodInfo.methodType().stringValue()), + (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, + dcb.bytecodesBufWriter.asByteBuffer().slice(0, dcb.bytecodesBufWriter.size()), + dcb.constantPool, + dcb.handlers); + } + private static final String OBJECT_INITIALIZER_NAME = ""; private static final int FLAG_THIS_UNINIT = 0x01; private static final int FRAME_DEFAULT_CAPACITY = 10; @@ -307,9 +308,9 @@ public final class StackMapGenerator { //patch bytecode bytecode.position(frame.offset); for (int n=1; n {} - case Classfile.RETURN -> { + case NOP -> {} + case RETURN -> { ncf = true; } - case Classfile.ACONST_NULL -> + case ACONST_NULL -> currentFrame.pushStack(Type.NULL_TYPE); - case Classfile.ICONST_M1, Classfile.ICONST_0, Classfile.ICONST_1, Classfile.ICONST_2, Classfile.ICONST_3, Classfile.ICONST_4, Classfile.ICONST_5, Classfile.SIPUSH, Classfile.BIPUSH -> + case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH -> currentFrame.pushStack(Type.INTEGER_TYPE); - case Classfile.LCONST_0, Classfile.LCONST_1 -> + case LCONST_0, LCONST_1 -> currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.FCONST_0, Classfile.FCONST_1, Classfile.FCONST_2 -> + case FCONST_0, FCONST_1, FCONST_2 -> currentFrame.pushStack(Type.FLOAT_TYPE); - case Classfile.DCONST_0, Classfile.DCONST_1 -> + case DCONST_0, DCONST_1 -> currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.LDC -> + case LDC -> processLdc(bcs.getIndexU1()); - case Classfile.LDC_W, Classfile.LDC2_W -> + case LDC_W, LDC2_W -> processLdc(bcs.getIndexU2()); - case Classfile.ILOAD -> + case ILOAD -> currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.INTEGER_TYPE); - case Classfile.ILOAD_0, Classfile.ILOAD_1, Classfile.ILOAD_2, Classfile.ILOAD_3 -> - currentFrame.checkLocal(opcode - Classfile.ILOAD_0).pushStack(Type.INTEGER_TYPE); - case Classfile.LLOAD -> + case ILOAD_0, ILOAD_1, ILOAD_2, ILOAD_3 -> + currentFrame.checkLocal(opcode - ILOAD_0).pushStack(Type.INTEGER_TYPE); + case LLOAD -> currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.LLOAD_0, Classfile.LLOAD_1, Classfile.LLOAD_2, Classfile.LLOAD_3 -> - currentFrame.checkLocal(opcode - Classfile.LLOAD_0 + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.FLOAD -> + case LLOAD_0, LLOAD_1, LLOAD_2, LLOAD_3 -> + currentFrame.checkLocal(opcode - LLOAD_0 + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case FLOAD -> currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.FLOAT_TYPE); - case Classfile.FLOAD_0, Classfile.FLOAD_1, Classfile.FLOAD_2, Classfile.FLOAD_3 -> - currentFrame.checkLocal(opcode - Classfile.FLOAD_0).pushStack(Type.FLOAT_TYPE); - case Classfile.DLOAD -> + case FLOAD_0, FLOAD_1, FLOAD_2, FLOAD_3 -> + currentFrame.checkLocal(opcode - FLOAD_0).pushStack(Type.FLOAT_TYPE); + case DLOAD -> currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.DLOAD_0, Classfile.DLOAD_1, Classfile.DLOAD_2, Classfile.DLOAD_3 -> - currentFrame.checkLocal(opcode - Classfile.DLOAD_0 + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.ALOAD -> + case DLOAD_0, DLOAD_1, DLOAD_2, DLOAD_3 -> + currentFrame.checkLocal(opcode - DLOAD_0 + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case ALOAD -> currentFrame.pushStack(currentFrame.getLocal(bcs.getIndex())); - case Classfile.ALOAD_0, Classfile.ALOAD_1, Classfile.ALOAD_2, Classfile.ALOAD_3 -> - currentFrame.pushStack(currentFrame.getLocal(opcode - Classfile.ALOAD_0)); - case Classfile.IALOAD, Classfile.BALOAD, Classfile.CALOAD, Classfile.SALOAD -> + case ALOAD_0, ALOAD_1, ALOAD_2, ALOAD_3 -> + currentFrame.pushStack(currentFrame.getLocal(opcode - ALOAD_0)); + case IALOAD, BALOAD, CALOAD, SALOAD -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); - case Classfile.LALOAD -> + case LALOAD -> currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.FALOAD -> + case FALOAD -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); - case Classfile.DALOAD -> + case DALOAD -> currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.AALOAD -> + case AALOAD -> currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()) == Type.NULL_TYPE ? Type.NULL_TYPE : type1.getComponent()); - case Classfile.ISTORE -> + case ISTORE -> currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.INTEGER_TYPE); - case Classfile.ISTORE_0, Classfile.ISTORE_1, Classfile.ISTORE_2, Classfile.ISTORE_3 -> - currentFrame.decStack(1).setLocal(opcode - Classfile.ISTORE_0, Type.INTEGER_TYPE); - case Classfile.LSTORE -> + case ISTORE_0, ISTORE_1, ISTORE_2, ISTORE_3 -> + currentFrame.decStack(1).setLocal(opcode - ISTORE_0, Type.INTEGER_TYPE); + case LSTORE -> currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.LSTORE_0, Classfile.LSTORE_1, Classfile.LSTORE_2, Classfile.LSTORE_3 -> - currentFrame.decStack(2).setLocal2(opcode - Classfile.LSTORE_0, Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.FSTORE -> + case LSTORE_0, LSTORE_1, LSTORE_2, LSTORE_3 -> + currentFrame.decStack(2).setLocal2(opcode - LSTORE_0, Type.LONG_TYPE, Type.LONG2_TYPE); + case FSTORE -> currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.FLOAT_TYPE); - case Classfile.FSTORE_0, Classfile.FSTORE_1, Classfile.FSTORE_2, Classfile.FSTORE_3 -> - currentFrame.decStack(1).setLocal(opcode - Classfile.FSTORE_0, Type.FLOAT_TYPE); - case Classfile.DSTORE -> + case FSTORE_0, FSTORE_1, FSTORE_2, FSTORE_3 -> + currentFrame.decStack(1).setLocal(opcode - FSTORE_0, Type.FLOAT_TYPE); + case DSTORE -> currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.DSTORE_0, Classfile.DSTORE_1, Classfile.DSTORE_2, Classfile.DSTORE_3 -> - currentFrame.decStack(2).setLocal2(opcode - Classfile.DSTORE_0, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.ASTORE -> + case DSTORE_0, DSTORE_1, DSTORE_2, DSTORE_3 -> + currentFrame.decStack(2).setLocal2(opcode - DSTORE_0, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case ASTORE -> currentFrame.setLocal(bcs.getIndex(), currentFrame.popStack()); - case Classfile.ASTORE_0, Classfile.ASTORE_1, Classfile.ASTORE_2, Classfile.ASTORE_3 -> - currentFrame.setLocal(opcode - Classfile.ASTORE_0, currentFrame.popStack()); - case Classfile.LASTORE, Classfile.DASTORE -> + case ASTORE_0, ASTORE_1, ASTORE_2, ASTORE_3 -> + currentFrame.setLocal(opcode - ASTORE_0, currentFrame.popStack()); + case LASTORE, DASTORE -> currentFrame.decStack(4); - case Classfile.IASTORE, Classfile.BASTORE, Classfile.CASTORE, Classfile.SASTORE, Classfile.FASTORE, Classfile.AASTORE -> + case IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE -> currentFrame.decStack(3); - case Classfile.POP, Classfile.MONITORENTER, Classfile.MONITOREXIT -> + case POP, MONITORENTER, MONITOREXIT -> currentFrame.decStack(1); - case Classfile.POP2 -> + case POP2 -> currentFrame.decStack(2); - case Classfile.DUP -> + case DUP -> currentFrame.pushStack(type1 = currentFrame.popStack()).pushStack(type1); - case Classfile.DUP_X1 -> { + case DUP_X1 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); currentFrame.pushStack(type1).pushStack(type2).pushStack(type1); } - case Classfile.DUP_X2 -> { + case DUP_X2 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); type3 = currentFrame.popStack(); currentFrame.pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); } - case Classfile.DUP2 -> { + case DUP2 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); currentFrame.pushStack(type2).pushStack(type1).pushStack(type2).pushStack(type1); } - case Classfile.DUP2_X1 -> { + case DUP2_X1 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); type3 = currentFrame.popStack(); currentFrame.pushStack(type2).pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); } - case Classfile.DUP2_X2 -> { + case DUP2_X2 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); type3 = currentFrame.popStack(); type4 = currentFrame.popStack(); currentFrame.pushStack(type2).pushStack(type1).pushStack(type4).pushStack(type3).pushStack(type2).pushStack(type1); } - case Classfile.SWAP -> { + case SWAP -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); currentFrame.pushStack(type1); currentFrame.pushStack(type2); } - case Classfile.IADD, Classfile.ISUB, Classfile.IMUL, Classfile.IDIV, Classfile.IREM, Classfile.ISHL, Classfile.ISHR, Classfile.IUSHR, Classfile.IOR, Classfile.IXOR, Classfile.IAND -> + case IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); - case Classfile.INEG, Classfile.ARRAYLENGTH, Classfile.INSTANCEOF -> + case INEG, ARRAYLENGTH, INSTANCEOF -> currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); - case Classfile.LADD, Classfile.LSUB, Classfile.LMUL, Classfile.LDIV, Classfile.LREM, Classfile.LAND, Classfile.LOR, Classfile.LXOR -> + case LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR -> currentFrame.decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.LNEG -> + case LNEG -> currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.LSHL, Classfile.LSHR, Classfile.LUSHR -> + case LSHL, LSHR, LUSHR -> currentFrame.decStack(3).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.FADD, Classfile.FSUB, Classfile.FMUL, Classfile.FDIV, Classfile.FREM -> + case FADD, FSUB, FMUL, FDIV, FREM -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); - case Classfile.FNEG -> + case FNEG -> currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); - case Classfile.DADD, Classfile.DSUB, Classfile.DMUL, Classfile.DDIV, Classfile.DREM -> + case DADD, DSUB, DMUL, DDIV, DREM -> currentFrame.decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.DNEG -> + case DNEG -> currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.IINC -> + case IINC -> currentFrame.checkLocal(bcs.getIndex()); - case Classfile.I2L -> + case I2L -> currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.L2I -> + case L2I -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); - case Classfile.I2F -> + case I2F -> currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); - case Classfile.I2D -> + case I2D -> currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.L2F -> + case L2F -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); - case Classfile.L2D -> + case L2D -> currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.F2I -> + case F2I -> currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); - case Classfile.F2L -> + case F2L -> currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.F2D -> + case F2D -> currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); - case Classfile.D2L -> + case D2L -> currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); - case Classfile.D2F -> + case D2F -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); - case Classfile.I2B, Classfile.I2C, Classfile.I2S -> + case I2B, I2C, I2S -> currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); - case Classfile.LCMP, Classfile.DCMPL, Classfile.DCMPG -> + case LCMP, DCMPL, DCMPG -> currentFrame.decStack(4).pushStack(Type.INTEGER_TYPE); - case Classfile.FCMPL, Classfile.FCMPG, Classfile.D2I -> + case FCMPL, FCMPG, D2I -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); - case Classfile.IF_ICMPEQ, Classfile.IF_ICMPNE, Classfile.IF_ICMPLT, Classfile.IF_ICMPGE, Classfile.IF_ICMPGT, Classfile.IF_ICMPLE, Classfile.IF_ACMPEQ, Classfile.IF_ACMPNE -> + case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> checkJumpTarget(currentFrame.decStack(2), bcs.dest()); - case Classfile.IFEQ, Classfile.IFNE, Classfile.IFLT, Classfile.IFGE, Classfile.IFGT, Classfile.IFLE, Classfile.IFNULL, Classfile.IFNONNULL -> + case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> checkJumpTarget(currentFrame.decStack(1), bcs.dest()); - case Classfile.GOTO -> { + case GOTO -> { checkJumpTarget(currentFrame, bcs.dest()); ncf = true; } - case Classfile.GOTO_W -> { + case GOTO_W -> { checkJumpTarget(currentFrame, bcs.destW()); ncf = true; } - case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> { + case TABLESWITCH, LOOKUPSWITCH -> { processSwitch(bcs); ncf = true; } - case Classfile.LRETURN, Classfile.DRETURN -> { + case LRETURN, DRETURN -> { currentFrame.decStack(2); ncf = true; } - case Classfile.IRETURN, Classfile.FRETURN, Classfile.ARETURN, Classfile.ATHROW -> { + case IRETURN, FRETURN, ARETURN, ATHROW -> { currentFrame.decStack(1); ncf = true; } - case Classfile.GETSTATIC, Classfile.PUTSTATIC, Classfile.GETFIELD, Classfile.PUTFIELD -> + case GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD -> processFieldInstructions(bcs); - case Classfile.INVOKEVIRTUAL, Classfile.INVOKESPECIAL, Classfile.INVOKESTATIC, Classfile.INVOKEINTERFACE, Classfile.INVOKEDYNAMIC -> + case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> this_uninit = processInvokeInstructions(bcs, (bci >= exMin && bci < exMax), this_uninit); - case Classfile.NEW -> + case NEW -> currentFrame.pushStack(Type.uninitializedType(bci)); - case Classfile.NEWARRAY -> + case NEWARRAY -> currentFrame.decStack(1).pushStack(getNewarrayType(bcs.getIndex())); - case Classfile.ANEWARRAY -> + case ANEWARRAY -> processAnewarray(bcs.getIndexU2()); - case Classfile.CHECKCAST -> + case CHECKCAST -> currentFrame.decStack(1).pushStack(cpIndexToType(bcs.getIndexU2(), cp)); - case Classfile.MULTIANEWARRAY -> { + case MULTIANEWARRAY -> { type1 = cpIndexToType(bcs.getIndexU2(), cp); int dim = bcs.getU1(bcs.bci + 3); for (int i = 0; i < dim; i++) { @@ -638,6 +639,8 @@ public final class StackMapGenerator { } currentFrame.pushStack(type1); } + case JSR, JSR_W, RET -> + generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0"); default -> generatorError(String.format("Bad instruction: %02x", opcode)); } @@ -694,7 +697,7 @@ public final class StackMapGenerator { int defaultOfset = bcs.getInt(alignedBci); int keys, delta; currentFrame.popStack(); - if (bcs.rawCode == Classfile.TABLESWITCH) { + if (bcs.rawCode == TABLESWITCH) { int low = bcs.getInt(alignedBci + 4); int high = bcs.getInt(alignedBci + 2 * 4); if (low > high) { @@ -731,17 +734,17 @@ public final class StackMapGenerator { private void processFieldInstructions(RawBytecodeHelper bcs) { var desc = ClassDesc.ofDescriptor(((MemberRefEntry)cp.entryByIndex(bcs.getIndexU2())).nameAndType().type().stringValue()); switch (bcs.rawCode) { - case Classfile.GETSTATIC -> + case GETSTATIC -> currentFrame.pushStack(desc); - case Classfile.PUTSTATIC -> { + case PUTSTATIC -> { currentFrame.popStack(); if (isDoubleSlot(desc)) currentFrame.popStack(); } - case Classfile.GETFIELD -> { + case GETFIELD -> { currentFrame.popStack(); currentFrame.pushStack(desc); } - case Classfile.PUTFIELD -> { + case PUTFIELD -> { currentFrame.popStack(); currentFrame.popStack(); if (isDoubleSlot(desc)) currentFrame.popStack(); @@ -754,7 +757,7 @@ public final class StackMapGenerator { int index = bcs.getIndexU2(); int opcode = bcs.rawCode; var cpe = cp.entryByIndex(index); - var nameAndType = opcode == Classfile.INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); + var nameAndType = opcode == INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); String invokeMethodName = nameAndType.name().stringValue(); var mDesc = nameAndType.type().stringValue(); @@ -786,7 +789,7 @@ public final class StackMapGenerator { int bci = bcs.bci; currentFrame.decStack(nargs); - if (opcode != Classfile.INVOKESTATIC && opcode != Classfile.INVOKEDYNAMIC) { + if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { Type type = currentFrame.popStack(); if (type == Type.UNITIALIZED_THIS_TYPE) { @@ -845,8 +848,9 @@ public final class StackMapGenerator { methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")))); //try to attach debug info about corrupted bytecode to the message try { - cp.options.generateStackmaps = false; - var clb = new DirectClassBuilder(cp, cp.classEntry(ClassDesc.of("FakeClass"))); + //clone SplitConstantPool with alternate Options + var newCp = new SplitConstantPool(cp, new Options(List.of(Classfile.Option.generateStackmap(false)))); + var clb = new DirectClassBuilder(newCp, newCp.classEntry(ClassDesc.of("FakeClass"))); clb.withMethod(methodName, methodDesc, isStatic ? ACC_STATIC : 0, mb -> ((DirectMethodBuilder)mb).writeAttribute(new UnboundAttribute.AdHocAttribute(Attributes.CODE) { @Override @@ -899,26 +903,26 @@ public final class StackMapGenerator { offsets.set(bci); } no_control_flow = switch (opcode) { - case Classfile.GOTO -> { + case GOTO -> { offsets.set(bcs.dest()); yield true; } - case Classfile.GOTO_W -> { + case GOTO_W -> { offsets.set(bcs.destW()); yield true; } - case Classfile.IF_ICMPEQ, Classfile.IF_ICMPNE, Classfile.IF_ICMPLT, Classfile.IF_ICMPGE, - Classfile.IF_ICMPGT, Classfile.IF_ICMPLE, Classfile.IFEQ, Classfile.IFNE, - Classfile.IFLT, Classfile.IFGE, Classfile.IFGT, Classfile.IFLE, Classfile.IF_ACMPEQ, - Classfile.IF_ACMPNE , Classfile.IFNULL , Classfile.IFNONNULL -> { + case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, + IF_ICMPGT, IF_ICMPLE, IFEQ, IFNE, + IFLT, IFGE, IFGT, IFLE, IF_ACMPEQ, + IF_ACMPNE , IFNULL , IFNONNULL -> { offsets.set(bcs.dest()); yield false; } - case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> { + case TABLESWITCH, LOOKUPSWITCH -> { int aligned_bci = RawBytecodeHelper.align(bci + 1); int default_ofset = bcs.getInt(aligned_bci); int keys, delta; - if (bcs.rawCode == Classfile.TABLESWITCH) { + if (bcs.rawCode == TABLESWITCH) { int low = bcs.getInt(aligned_bci + 4); int high = bcs.getInt(aligned_bci + 2 * 4); keys = high - low + 1; @@ -933,8 +937,8 @@ public final class StackMapGenerator { } yield true; } - case Classfile.IRETURN, Classfile.LRETURN, Classfile.FRETURN, Classfile.DRETURN, - Classfile.ARETURN, Classfile.RETURN, Classfile.ATHROW -> true; + case IRETURN, LRETURN, FRETURN, DRETURN, + ARETURN, RETURN, ATHROW -> true; default -> false; }; } catch (IllegalArgumentException iae) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/DiscontinuedInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/DiscontinuedInstruction.java new file mode 100644 index 00000000000..be708533025 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/DiscontinuedInstruction.java @@ -0,0 +1,117 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models instruction discontinued from the {@code code} array of a {@code Code} + * attribute. Delivered as a {@link CodeElement} when traversing the elements of + * a {@link CodeModel}. + */ +public sealed interface DiscontinuedInstruction extends Instruction { + + /** + * Models JSR and JSR_W instructions discontinued from the {@code code} + * array of a {@code Code} attribute since class file version 51.0. + * Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#DISCONTINUED_JSR}. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ + sealed interface JsrInstruction extends DiscontinuedInstruction + permits AbstractInstruction.BoundJsrInstruction, + AbstractInstruction.UnboundJsrInstruction { + + /** + * {@return the target of the JSR instruction} + */ + Label target(); + + /** + * {@return a JSR instruction} + * + * @param op the opcode for the specific type of JSR instruction, + * which must be of kind {@link Opcode.Kind#DISCONTINUED_JSR} + * @param target target label of the subroutine + */ + static JsrInstruction of(Opcode op, Label target) { + Util.checkKind(op, Opcode.Kind.DISCONTINUED_JSR); + return new AbstractInstruction.UnboundJsrInstruction(op, target); + } + + /** + * {@return a JSR instruction} + * + * @param target target label of the subroutine + */ + static JsrInstruction of(Label target) { + return of(Opcode.JSR, target); + } + } + + /** + * Models RET and RET_W instructions discontinued from the {@code code} + * array of a {@code Code} attribute since class file version 51.0. + * Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#DISCONTINUED_RET}. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ + sealed interface RetInstruction extends DiscontinuedInstruction + permits AbstractInstruction.BoundRetInstruction, + AbstractInstruction.UnboundRetInstruction { + + /** + * {@return the local variable slot with return address} + */ + int slot(); + + /** + * {@return a RET or RET_W instruction} + * + * @param op the opcode for the specific type of RET instruction, + * which must be of kind {@link Opcode.Kind#DISCONTINUED_RET} + * @param slot the local variable slot to load return address from + */ + static RetInstruction of(Opcode op, int slot) { + Util.checkKind(op, Opcode.Kind.DISCONTINUED_RET); + return new AbstractInstruction.UnboundRetInstruction(op, slot); + } + + /** + * {@return a RET instruction} + * + * @param slot the local variable slot to load return address from + */ + static RetInstruction of(int slot) { + return of(slot < 256 ? Opcode.RET : Opcode.RET_W, slot); + } + } +} diff --git a/test/jdk/jdk/classfile/CorpusTest.java b/test/jdk/jdk/classfile/CorpusTest.java index 2cf3cb9817f..03f399cd4f2 100644 --- a/test/jdk/jdk/classfile/CorpusTest.java +++ b/test/jdk/jdk/classfile/CorpusTest.java @@ -58,6 +58,7 @@ import jdk.internal.classfile.Attributes; import jdk.internal.classfile.BufWriter; import jdk.internal.classfile.Classfile; import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.CodeTransform; import jdk.internal.classfile.constantpool.ConstantPool; import jdk.internal.classfile.constantpool.PoolEntry; import jdk.internal.classfile.constantpool.Utf8Entry; @@ -203,12 +204,29 @@ class CorpusTest { byte[] newBytes = Classfile.build( classModel.thisClass().asSymbol(), classModel::forEachElement); - var newModel = Classfile.parse(newBytes); + var newModel = Classfile.parse(newBytes, Classfile.Option.generateStackmap(false)); assertEqualsDeep(ClassRecord.ofClassModel(newModel, CompatibilityFilter.By_ClassBuilder), ClassRecord.ofClassModel(classModel, CompatibilityFilter.By_ClassBuilder), "ClassModel[%s] transformed by ClassBuilder (actual) vs ClassModel before transformation (expected)".formatted(path)); assertEmpty(newModel.verify(null)); + + //testing maxStack and maxLocals are calculated identically by StackMapGenerator and StackCounter + byte[] noStackMaps = newModel.transform(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)); + var noStackModel = Classfile.parse(noStackMaps); + var itStack = newModel.methods().iterator(); + var itNoStack = noStackModel.methods().iterator(); + while (itStack.hasNext()) { + assertTrue(itNoStack.hasNext()); + var m1 = itStack.next(); + var m2 = itNoStack.next(); + var text1 = m1.methodName().stringValue() + m1.methodType().stringValue() + ": " + + m1.code().map(c -> c.maxLocals() + " / " + c.maxStack()).orElse("-"); + var text2 = m2.methodName().stringValue() + m2.methodType().stringValue() + ": " + + m2.code().map(c -> c.maxLocals() + " / " + c.maxStack()).orElse("-"); + assertEquals(text1, text2); + } + assertFalse(itNoStack.hasNext()); } // @Test(enabled = false) diff --git a/test/jdk/jdk/classfile/DiscontinuedInstructionsTest.java b/test/jdk/jdk/classfile/DiscontinuedInstructionsTest.java new file mode 100644 index 00000000000..a71ffd873cc --- /dev/null +++ b/test/jdk/jdk/classfile/DiscontinuedInstructionsTest.java @@ -0,0 +1,96 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 + * @summary Testing Classfile handling JSR and RET instructions. + * @run junit DiscontinuedInstructionsTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.List; +import jdk.internal.classfile.*; +import jdk.internal.classfile.instruction.DiscontinuedInstruction; +import helpers.ByteArrayClassLoader; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static java.lang.constant.ConstantDescs.*; +import static jdk.internal.classfile.Classfile.*; + +class DiscontinuedInstructionsTest { + + @Test + void testJsrAndRetProcessing() throws Exception { + var testClass = "JsrAndRetSample"; + var testMethod = "testMethod"; + var cd_list = ArrayList.class.describeConstable().get(); + var bytes = Classfile.build(ClassDesc.of(testClass), clb -> clb + .withVersion(JAVA_5_VERSION, 0) + .withMethodBody(testMethod, MethodTypeDesc.of(CD_void, cd_list), ACC_PUBLIC | ACC_STATIC, cob -> cob + .block(bb -> { + bb.constantInstruction("Hello") + .with(DiscontinuedInstruction.JsrInstruction.of(bb.breakLabel())); + bb.constantInstruction("World") + .with(DiscontinuedInstruction.JsrInstruction.of(Opcode.JSR_W, bb.breakLabel())) + .return_(); + }) + .astore(355) + .aload(0) + .swap() + .invokevirtual(cd_list, "add", MethodTypeDesc.of(CD_boolean, CD_Object)) + .pop() + .with(DiscontinuedInstruction.RetInstruction.of(355)))); + + var c = Classfile.parse(bytes).methods().get(0).code().get(); + assertEquals(356, c.maxLocals()); + assertEquals(6, c.maxStack()); + + + var list = new ArrayList(); + new ByteArrayClassLoader(DiscontinuedInstructionsTest.class.getClassLoader(), testClass, bytes) + .getMethod(testClass, testMethod) + .invoke(null, list); + assertEquals(list, List.of("Hello", "World")); + + bytes = Classfile.parse(bytes).transform(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)); + + new ByteArrayClassLoader(DiscontinuedInstructionsTest.class.getClassLoader(), testClass, bytes) + .getMethod(testClass, testMethod) + .invoke(null, list); + assertEquals(list, List.of("Hello", "World", "Hello", "World")); + + var clm = Classfile.parse(bytes); + + //test failover stack map generation + clm.transform(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.endHandler(clb -> clb.withVersion(JAVA_6_VERSION, 0)))); + + //test failure of stack map generation + assertThrows(IllegalStateException.class, () -> + clm.transform(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.endHandler(clb -> clb.withVersion(JAVA_7_VERSION, 0))))); + } +} diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index 69e8eb0e62c..0bf368197cc 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -26,11 +26,13 @@ /* * @test * @summary Testing Classfile stack maps generator. + * @bug 8305990 * @build testdata.* * @run junit StackMapsTest */ -import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.*; +import jdk.internal.classfile.components.ClassPrinter; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; @@ -215,6 +217,20 @@ class StackMapsTest { .withFlags(AccessFlag.STATIC)))); } + @Test + void testClassVersions() throws Exception { + var actualVersion = Classfile.parse(StackMapsTest.class.getResourceAsStream("/testdata/Pattern1.class").readAllBytes()); + + //test transformation to class version 49 with removal of StackMapTable attributes + var version49 = Classfile.parse(actualVersion.transform(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.endHandler(clb -> clb.withVersion(49, 0))))); + assertFalse(ClassPrinter.toTree(version49, ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES).walk().anyMatch(n -> n.name().equals("stack map frames"))); + + //test transformation to class version 50 with re-generation of StackMapTable attributes + assertEmpty(Classfile.parse(version49.transform(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.endHandler(clb -> clb.withVersion(50, 0))))).verify(null)); + } + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); private static void testTransformedStackMaps(String classPath, Classfile.Option... options) throws Exception { diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index cd27c2ade60..f2441f86fc3 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -200,6 +200,10 @@ class RebuildingTransformation { default -> throw new AssertionError("Should not reach here"); } } + case DiscontinuedInstruction.JsrInstruction i -> + cob.with(DiscontinuedInstruction.JsrInstruction.of(i.opcode(), labels.computeIfAbsent(i.target(), l -> cob.newLabel()))); + case DiscontinuedInstruction.RetInstruction i -> + cob.with(DiscontinuedInstruction.RetInstruction.of(i.opcode(), i.slot())); case FieldInstruction i -> { if (pathSwitch.nextBoolean()) { switch (i.opcode()) {