8305990: Stripping debug info of ASM 9.5 fails

Reviewed-by: mcimadamore
This commit is contained in:
Adam Sotona 2023-05-09 13:13:17 +00:00
parent 040cb7b5a9
commit a05560d993
16 changed files with 962 additions and 177 deletions

@ -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,

@ -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;

@ -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());
}
}
}

@ -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

@ -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();

@ -242,8 +242,21 @@ public final class CodeImpl
private void inflateJumpTargets() {
Optional<StackMapTableAttribute> 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<codeEnd; ) {
var i = bcToInstruction(classReader.readU1(pos), pos);
switch (i) {
case BranchInstruction br -> br.target();
case DiscontinuedInstruction.JsrInstruction jsr -> jsr.target();
default -> {}
}
pos += i.sizeInBytes();
}
}
return;
}
@SuppressWarnings("unchecked")
int stackMapPos = ((BoundAttribute<StackMapTableAttribute>) 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)

@ -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

@ -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<CodeModel>
implements TerminalCodeBuilder, LabelContext {
private final List<CharacterRange> characterRanges = new ArrayList<>();
private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers = new ArrayList<>();
final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers = new ArrayList<>();
private final List<LocalVariable> localVariables = new ArrayList<>();
private final List<LocalVariableType> 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();

@ -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;

@ -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<Integer, Integer> 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<AbstractPseudoInstruction.ExceptionCatchImpl> 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()));
}
}

@ -140,21 +140,22 @@ import jdk.internal.classfile.attribute.CodeAttribute;
* <li>It works with only minimal mandatory stack map frames.
* <li>It does not spend time on any non-essential verifications.
* </ul>
* <p>
* In case of an exception during the Generator loop there is just minimal information available in the exception message.
* <p>
* 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 <code>java.lang.System</code> properties:<dl>
* <dt><code>-Djdk.internal.classfile.impl.StackMapGenerator.DEBUG=true</code>
* <dd>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.
* <dt><code>-Djdk.internal.classfile.impl.StackMapGenerator.TRACE=true</code>
* <dd>Activates full detailed tracing of the generator process for all invocations.
* </dl>
*/
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 = "<init>";
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<blockSize; n++) {
bytecode.put((byte) Classfile.NOP);
bytecode.put((byte) NOP);
}
bytecode.put((byte) Classfile.ATHROW);
bytecode.put((byte) ATHROW);
//patch handlers
removeRangeFromExcTable(frame.offset, frame.offset + blockSize);
}
@ -433,204 +434,204 @@ public final class StackMapGenerator {
verified_exc_handlers = true;
}
switch (opcode) {
case Classfile.NOP -> {}
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<CodeAttribute>(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) {

@ -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);
}
}
}

@ -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)

@ -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<String>();
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)))));
}
}

@ -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 {

@ -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()) {