8305990: Stripping debug info of ASM 9.5 fails
Reviewed-by: mcimadamore
This commit is contained in:
parent
040cb7b5a9
commit
a05560d993
src/java.base/share/classes/jdk/internal/classfile
Instruction.javaOpcode.java
impl
AbstractInstruction.javaBufWriterImpl.javaClassPrinterImpl.javaCodeImpl.javaDirectClassBuilder.javaDirectCodeBuilder.javaSplitConstantPool.javaStackCounter.javaStackMapGenerator.java
instruction
test/jdk/jdk/classfile
@ -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) {
|
||||
|
117
src/java.base/share/classes/jdk/internal/classfile/instruction/DiscontinuedInstruction.java
Normal file
117
src/java.base/share/classes/jdk/internal/classfile/instruction/DiscontinuedInstruction.java
Normal file
@ -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)
|
||||
|
96
test/jdk/jdk/classfile/DiscontinuedInstructionsTest.java
Normal file
96
test/jdk/jdk/classfile/DiscontinuedInstructionsTest.java
Normal file
@ -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()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user