All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.dmlloyd.classfile.impl.StackMapGenerator Maven / Gradle / Ivy

/*
 * Copyright (c) 2022, 2024, 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 io.github.dmlloyd.classfile.impl;

import io.github.dmlloyd.classfile.Attribute;
import io.github.dmlloyd.classfile.Attributes;
import io.github.dmlloyd.classfile.BufWriter;
import io.github.dmlloyd.classfile.ClassFile;
import io.github.dmlloyd.classfile.Label;
import io.github.dmlloyd.classfile.attribute.StackMapTableAttribute;
import io.github.dmlloyd.classfile.constantpool.ClassEntry;
import io.github.dmlloyd.classfile.constantpool.ConstantDynamicEntry;
import io.github.dmlloyd.classfile.constantpool.ConstantPoolBuilder;
import io.github.dmlloyd.classfile.constantpool.InvokeDynamicEntry;
import io.github.dmlloyd.classfile.constantpool.MemberRefEntry;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static io.github.dmlloyd.classfile.ClassFile.*;
import static java.lang.constant.ConstantDescs.*;

/**
 * StackMapGenerator is responsible for stack map frames generation.
 * 

* Stack map frames are computed from serialized bytecode similar way they are verified during class loading process. *

* The {@linkplain #generate() frames computation} consists of following steps: *

    *
  1. {@linkplain #detectFrameOffsets() Detection} of mandatory stack map frames offsets:
      *
    • Mandatory stack map frame offsets include all jump and switch instructions targets, * offsets immediately following {@linkplain #noControlFlow(int) "no control flow"} * and all exception table handlers. *
    • Detection is performed in a single fast pass through the bytecode, * with no auxiliary structures construction nor further instructions processing. *
    *
  2. Generator loop {@linkplain #processMethod() processing bytecode instructions}:
      *
    • Generator loop simulates sequence instructions {@linkplain #processBlock(RawBytecodeHelper) processing effect on the actual stack and locals}. *
    • All mandatory {@linkplain Frame frames} detected in the step #1 are {@linkplain Frame#checkAssignableTo(Frame) retro-filled} * (or {@linkplain Frame#merge(Type, Type[], int, Frame) reverse-merged} in subsequent processing) * with the actual stack and locals for all matching jump, switch and exception handler targets. *
    • All frames modified by reverse merges are marked as {@linkplain Frame#dirty dirty} for further processing. *
    • Code blocks with not yet known entry frame content are skipped and related frames are also marked as dirty. *
    • Generator loop process is repeated until all mandatory frames are cleared or until an error state is reached. *
    • Generator loop always passes all instructions at least once to calculate {@linkplain #maxStack max stack} * and {@linkplain #maxLocals max locals} code attributes. *
    • More than one pass is usually not necessary, except for more complex bytecode sequences.
      * (Note: experimental measurements showed that more than 99% of the cases required only single pass to clear all frames, * less than 1% of the cases required second pass and remaining 0,01% of the cases required third pass to clear all frames.). *
    *
  3. Dead code patching to pass class loading verification:
      *
    • Dead code blocks are indicated by frames remaining without content after leaving the Generator loop. *
    • Each dead code block is filled with NOP instructions, terminated with * ATHROW instruction, and removed from exception handlers table. *
    • Dead code block entry frame is set to java.lang.Throwable single stack item and no locals. *
    *
*

* {@linkplain Frame#merge(Type, Type[], int, Frame) Reverse-merge} of the stack map frames * may in some situations require to determine {@linkplain ClassHierarchyImpl class hierarchy} relations. *

* Reverse-merge of individual {@linkplain Type types} is performed when a target frame has already been retro-filled * and it is necessary to adjust its existing stack entries and locals to also match actual stack map frame conditions. * Following tables describe how new target stack entry or local type is calculated, based on the actual frame stack entry or local ("from") * and actual value of the target stack entry or local ("to"). * *

* *
Reverse-merge of general type categories
to \ fromTOPPRIMITIVEUNINITIALIZEDREFERENCE *
TOPTOPTOPTOPTOP *
PRIMITIVETOPReverse-merge of primitive typesTOPTOP *
UNINITIALIZEDTOPTOPIs NEW offset matching ? UNINITIALIZED : TOPTOP *
REFERENCETOPTOPTOPReverse-merge of reference types *
*

*

* *
Reverse-merge of primitive types
to \ fromSHORTBYTEBOOLEANLONGDOUBLEFLOATINTEGER *
SHORTSHORTTOPTOPTOPTOPTOPSHORT *
BYTETOPBYTETOPTOPTOPTOPBYTE *
BOOLEANTOPTOPBOOLEANTOPTOPTOPBOOLEAN *
LONGTOPTOPTOPLONGTOPTOPTOP *
DOUBLETOPTOPTOPTOPDOUBLETOPTOP *
FLOATTOPTOPTOPTOPTOPFLOATTOP *
INTEGERTOPTOPTOPTOPTOPTOPINTEGER *
*

*

* *
Reverse merge of reference types
to \ fromNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACE*OBJECT** *
NULLNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACEOBJECT *
j.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object *
j.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Objectj.l.Cloneablej.l.Cloneable *
j.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.l.Objectj.i.Serializablej.i.Serializable *
ARRAYARRAYj.l.Objectj.l.Objectj.l.ObjectReverse merge of arraysj.l.Objectj.l.Object *
INTERFACE*INTERFACEj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object *
OBJECT**OBJECTj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.ObjectResolved common ancestor *
*any interface reference except for j.l.Cloneable and j.i.Serializable
**any object reference except for j.l.Object *
*

* Array types are reverse-merged as reference to array type constructed from reverse-merged components. * Reference to j.l.Object is an alternate result when construction of the array type is not possible (when reverse-merge of components returned TOP or other non-reference and non-primitive type). *

* Custom class hierarchy resolver has been implemented as a part of the library to avoid heavy class loading * and to allow stack maps generation even for code with incomplete dependency classpath. * However stack maps generated with {@linkplain ClassHierarchyImpl#resolve(ClassDesc) warnings of unresolved dependencies} may later fail to verify during class loading process. *

* Focus of the whole algorithm is on high performance and low memory footprint:

    *
  • It does not produce, collect nor visit any complex intermediate structures * (beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}). *
  • It works with only minimal mandatory stack map frames. *
  • It does not spend time on any non-essential verifications. *
*/ public final class StackMapGenerator { static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) { return new StackMapGenerator( dcb, buf.thisClass().asSymbol(), dcb.methodInfo.methodName().stringValue(), dcb.methodInfo.methodTypeSymbol(), (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, dcb.bytecodesBufWriter.bytecodeView(), dcb.constantPool, dcb.context, dcb.handlers); } private static final String OBJECT_INITIALIZER_NAME = ""; private static final int FLAG_THIS_UNINIT = 0x01; private static final int FRAME_DEFAULT_CAPACITY = 10; private static final int T_BOOLEAN = 4, T_LONG = 11; private static final int ITEM_TOP = 0, ITEM_INTEGER = 1, ITEM_FLOAT = 2, ITEM_DOUBLE = 3, ITEM_LONG = 4, ITEM_NULL = 5, ITEM_UNINITIALIZED_THIS = 6, ITEM_OBJECT = 7, ITEM_UNINITIALIZED = 8, ITEM_BOOLEAN = 9, ITEM_BYTE = 10, ITEM_SHORT = 11, ITEM_CHAR = 12, ITEM_LONG_2ND = 13, ITEM_DOUBLE_2ND = 14; private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null, Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE, Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE}; static record RawExceptionCatch(int start, int end, int handler, Type catchType) {} private final Type thisType; private final String methodName; private final MethodTypeDesc methodDesc; private final RawBytecodeHelper.CodeRange bytecode; private final SplitConstantPool cp; private final boolean isStatic; private final LabelContext labelContext; private final List handlers; private final List rawHandlers; private final ClassHierarchyImpl classHierarchy; private final boolean patchDeadCode; private final boolean filterDeadLabels; private List frames; private final Frame currentFrame; private int maxStack, maxLocals; /** * Primary constructor of the Generator class. * New Generator instance must be created for each individual class/method. * Instance contains only immutable results, all the calculations are processed during instance construction. * * @param labelContext LabelContext instance used to resolve or patch ExceptionHandler * labels to bytecode offsets (or vice versa) * @param thisClass class to generate stack maps for * @param methodName method name to generate stack maps for * @param methodDesc method descriptor to generate stack maps for * @param isStatic information whether the method is static * @param bytecode R/W ByteBuffer wrapping method bytecode, the content is altered in case Generator detects and patches dead code * @param cp R/W ConstantPoolBuilder instance used to resolve all involved CP entries and also generate new entries referenced from the generated stack maps * @param handlers R/W ExceptionHandler list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers * and also to be altered when dead code is detected and must be excluded from exception handlers */ public StackMapGenerator(LabelContext labelContext, ClassDesc thisClass, String methodName, MethodTypeDesc methodDesc, boolean isStatic, RawBytecodeHelper.CodeRange bytecode, SplitConstantPool cp, ClassFileImpl context, List handlers) { this.thisType = Type.referenceType(thisClass); this.methodName = methodName; this.methodDesc = methodDesc; this.isStatic = isStatic; this.bytecode = bytecode; this.cp = cp; this.labelContext = labelContext; this.handlers = handlers; this.rawHandlers = new ArrayList<>(handlers.size()); this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver()); this.patchDeadCode = context.patchDeadCode(); this.filterDeadLabels = context.dropDeadLabels(); this.currentFrame = new Frame(classHierarchy); generate(); } /** * 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 Frame getFrame(int offset) { //binary search over frames ordered by offset int low = 0; int high = frames.size() - 1; while (low <= high) { int mid = (low + high) >>> 1; var f = frames.get(mid); if (f.offset < offset) low = mid + 1; else if (f.offset > offset) high = mid - 1; else return f; } return null; } private void checkJumpTarget(Frame frame, int target) { frame.checkAssignableTo(getFrame(target)); } private int exMin, exMax; private boolean isAnyFrameDirty() { for (var f : frames) { if (f.dirty) return true; } return false; } private void generate() { exMin = bytecode.length(); exMax = -1; for (var exhandler : handlers) { int start_pc = labelContext.labelToBci(exhandler.tryStart()); int end_pc = labelContext.labelToBci(exhandler.tryEnd()); int handler_pc = labelContext.labelToBci(exhandler.handler()); if (start_pc >= 0 && end_pc >= 0 && end_pc > start_pc && handler_pc >= 0) { if (start_pc < exMin) exMin = start_pc; if (end_pc > exMax) exMax = end_pc; var catchType = exhandler.catchType(); rawHandlers.add(new RawExceptionCatch(start_pc, end_pc, handler_pc, catchType.isPresent() ? cpIndexToType(catchType.get().index(), cp) : Type.THROWABLE_TYPE)); } } BitSet frameOffsets = detectFrameOffsets(); int framesCount = frameOffsets.cardinality(); frames = new ArrayList<>(framesCount); int offset = -1; for (int i = 0; i < framesCount; i++) { offset = frameOffsets.nextSetBit(offset + 1); frames.add(new Frame(offset, classHierarchy)); } do { processMethod(); } while (isAnyFrameDirty()); maxLocals = currentFrame.frameMaxLocals; maxStack = currentFrame.frameMaxStack; //dead code patching for (int i = 0; i < framesCount; i++) { var frame = frames.get(i); if (frame.flags == -1) { if (!patchDeadCode) throw generatorError("Unable to generate stack map frame for dead code", frame.offset); //patch frame frame.pushStack(Type.THROWABLE_TYPE); if (maxStack < 1) maxStack = 1; int end = (i < framesCount - 1 ? frames.get(i + 1).offset : bytecode.length()) - 1; //patch bytecode var arr = bytecode.array(); Arrays.fill(arr, frame.offset, end, (byte) NOP); arr[end] = (byte) ATHROW; //patch handlers removeRangeFromExcTable(frame.offset, end + 1); } } } private void removeRangeFromExcTable(int rangeStart, int rangeEnd) { var it = handlers.listIterator(); while (it.hasNext()) { var e = it.next(); int handlerStart = labelContext.labelToBci(e.tryStart()); int handlerEnd = labelContext.labelToBci(e.tryEnd()); if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) { //out of range continue; } if (rangeStart <= handlerStart) { if (rangeEnd >= handlerEnd) { //complete removal it.remove(); } else { //cut from left Label newStart = labelContext.newLabel(); labelContext.setLabelTarget(newStart, rangeEnd); it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType())); } } else if (rangeEnd >= handlerEnd) { //cut from right Label newEnd = labelContext.newLabel(); labelContext.setLabelTarget(newEnd, rangeStart); it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); } else { //split Label newStart = labelContext.newLabel(); labelContext.setLabelTarget(newStart, rangeEnd); Label newEnd = labelContext.newLabel(); labelContext.setLabelTarget(newEnd, rangeStart); it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType())); } } } /** * Getter of the generated StackMapTableAttribute or null if stack map is empty * @return StackMapTableAttribute or null if stack map is empty */ public Attribute stackMapTableAttribute() { return frames.isEmpty() ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) { @Override public void writeBody(BufWriterImpl b) { b.writeU2(frames.size()); Frame prevFrame = new Frame(classHierarchy); prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); prevFrame.trimAndCompress(); for (var fr : frames) { fr.trimAndCompress(); fr.writeTo(b, prevFrame, cp); prevFrame = fr; } } }; } private static Type cpIndexToType(int index, ConstantPoolBuilder cp) { return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol()); } private void processMethod() { currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); currentFrame.stackSize = 0; currentFrame.flags = 0; currentFrame.offset = -1; int stackmapIndex = 0; var bcs = bytecode.start(); boolean ncf = false; while (bcs.next()) { currentFrame.offset = bcs.bci(); if (stackmapIndex < frames.size()) { int thisOffset = frames.get(stackmapIndex).offset; if (ncf && thisOffset > bcs.bci()) { throw generatorError("Expecting a stack map frame"); } if (thisOffset == bcs.bci()) { if (!ncf) { currentFrame.checkAssignableTo(frames.get(stackmapIndex)); } Frame nextFrame = frames.get(stackmapIndex++); while (!nextFrame.dirty) { //skip unmatched frames if (stackmapIndex == frames.size()) return; //skip the rest of this round nextFrame = frames.get(stackmapIndex++); } bcs.reset(nextFrame.offset); //skip code up-to the next frame bcs.next(); currentFrame.offset = bcs.bci(); currentFrame.copyFrom(nextFrame); nextFrame.dirty = false; } else if (thisOffset < bcs.bci()) { throw new ClassFormatError(String.format("Bad stack map offset %d", thisOffset)); } } else if (ncf) { throw generatorError("Expecting a stack map frame"); } ncf = processBlock(bcs); } } private boolean processBlock(RawBytecodeHelper bcs) { int opcode = bcs.opcode(); boolean ncf = false; boolean this_uninit = false; boolean verified_exc_handlers = false; int bci = bcs.bci(); Type type1, type2, type3, type4; if (RawBytecodeHelper.isStoreIntoLocal(opcode) && bci >= exMin && bci < exMax) { processExceptionHandlerTargets(bci, this_uninit); verified_exc_handlers = true; } switch (opcode) { case NOP -> {} case RETURN -> { ncf = true; } case ACONST_NULL -> currentFrame.pushStack(Type.NULL_TYPE); case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH -> currentFrame.pushStack(Type.INTEGER_TYPE); case LCONST_0, LCONST_1 -> currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case FCONST_0, FCONST_1, FCONST_2 -> currentFrame.pushStack(Type.FLOAT_TYPE); case DCONST_0, DCONST_1 -> currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case LDC -> processLdc(bcs.getIndexU1()); case LDC_W, LDC2_W -> processLdc(bcs.getIndexU2()); case ILOAD -> currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.INTEGER_TYPE); 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 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 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 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 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 LALOAD -> currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case FALOAD -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); case DALOAD -> currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case AALOAD -> currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()) == Type.NULL_TYPE ? Type.NULL_TYPE : type1.getComponent()); case ISTORE -> currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.INTEGER_TYPE); 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 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 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 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 ASTORE_0, ASTORE_1, ASTORE_2, ASTORE_3 -> currentFrame.setLocal(opcode - ASTORE_0, currentFrame.popStack()); case LASTORE, DASTORE -> currentFrame.decStack(4); case IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE -> currentFrame.decStack(3); case POP, MONITORENTER, MONITOREXIT -> currentFrame.decStack(1); case POP2 -> currentFrame.decStack(2); case DUP -> currentFrame.pushStack(type1 = currentFrame.popStack()).pushStack(type1); case DUP_X1 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); currentFrame.pushStack(type1).pushStack(type2).pushStack(type1); } case DUP_X2 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); type3 = currentFrame.popStack(); currentFrame.pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); } case DUP2 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); currentFrame.pushStack(type2).pushStack(type1).pushStack(type2).pushStack(type1); } case DUP2_X1 -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); type3 = currentFrame.popStack(); currentFrame.pushStack(type2).pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); } 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 SWAP -> { type1 = currentFrame.popStack(); type2 = currentFrame.popStack(); currentFrame.pushStack(type1); currentFrame.pushStack(type2); } case IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); case INEG, ARRAYLENGTH, INSTANCEOF -> currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); case LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR -> currentFrame.decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case LNEG -> currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case LSHL, LSHR, LUSHR -> currentFrame.decStack(3).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case FADD, FSUB, FMUL, FDIV, FREM -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); case FNEG -> currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); case DADD, DSUB, DMUL, DDIV, DREM -> currentFrame.decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case DNEG -> currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case IINC -> currentFrame.checkLocal(bcs.getIndex()); case I2L -> currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case L2I -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); case I2F -> currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); case I2D -> currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case L2F -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); case L2D -> currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case F2I -> currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); case F2L -> currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case F2D -> currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case D2L -> currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case D2F -> currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); case I2B, I2C, I2S -> currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); case LCMP, DCMPL, DCMPG -> currentFrame.decStack(4).pushStack(Type.INTEGER_TYPE); case FCMPL, FCMPG, D2I -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> checkJumpTarget(currentFrame.decStack(2), bcs.dest()); case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> checkJumpTarget(currentFrame.decStack(1), bcs.dest()); case GOTO -> { checkJumpTarget(currentFrame, bcs.dest()); ncf = true; } case GOTO_W -> { checkJumpTarget(currentFrame, bcs.destW()); ncf = true; } case TABLESWITCH, LOOKUPSWITCH -> { processSwitch(bcs); ncf = true; } case LRETURN, DRETURN -> { currentFrame.decStack(2); ncf = true; } case IRETURN, FRETURN, ARETURN, ATHROW -> { currentFrame.decStack(1); ncf = true; } case GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD -> processFieldInstructions(bcs); case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> this_uninit = processInvokeInstructions(bcs, (bci >= exMin && bci < exMax), this_uninit); case NEW -> currentFrame.pushStack(Type.uninitializedType(bci)); case NEWARRAY -> currentFrame.decStack(1).pushStack(getNewarrayType(bcs.getIndex())); case ANEWARRAY -> processAnewarray(bcs.getIndexU2()); case CHECKCAST -> currentFrame.decStack(1).pushStack(cpIndexToType(bcs.getIndexU2(), cp)); case MULTIANEWARRAY -> { type1 = cpIndexToType(bcs.getIndexU2(), cp); int dim = bcs.getU1Unchecked(bcs.bci() + 3); for (int i = 0; i < dim; i++) { currentFrame.popStack(); } currentFrame.pushStack(type1); } case JSR, JSR_W, RET -> throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0"); default -> throw generatorError(String.format("Bad instruction: %02x", opcode)); } if (!verified_exc_handlers && bci >= exMin && bci < exMax) { processExceptionHandlerTargets(bci, this_uninit); } return ncf; } private void processExceptionHandlerTargets(int bci, boolean this_uninit) { for (var ex : rawHandlers) { if (bci == ex.start || (currentFrame.localsChanged && bci > ex.start && bci < ex.end)) { int flags = currentFrame.flags; if (this_uninit) flags |= FLAG_THIS_UNINIT; Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType); checkJumpTarget(newFrame, ex.handler); } } currentFrame.localsChanged = false; } private void processLdc(int index) { switch (cp.entryByIndex(index).tag()) { case TAG_UTF8 -> currentFrame.pushStack(Type.OBJECT_TYPE); case TAG_STRING -> currentFrame.pushStack(Type.STRING_TYPE); case TAG_CLASS -> currentFrame.pushStack(Type.CLASS_TYPE); case TAG_INTEGER -> currentFrame.pushStack(Type.INTEGER_TYPE); case TAG_FLOAT -> currentFrame.pushStack(Type.FLOAT_TYPE); case TAG_DOUBLE -> currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case TAG_LONG -> currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case TAG_METHODHANDLE -> currentFrame.pushStack(Type.METHOD_HANDLE_TYPE); case TAG_METHODTYPE -> currentFrame.pushStack(Type.METHOD_TYPE); case TAG_CONSTANTDYNAMIC -> currentFrame.pushStack(cp.entryByIndex(index, ConstantDynamicEntry.class).asSymbol().constantType()); default -> throw generatorError("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag())); } } private void processSwitch(RawBytecodeHelper bcs) { int bci = bcs.bci(); int alignedBci = RawBytecodeHelper.align(bci + 1); int defaultOffset = bcs.getIntUnchecked(alignedBci); int keys, delta; currentFrame.popStack(); if (bcs.opcode() == TABLESWITCH) { int low = bcs.getIntUnchecked(alignedBci + 4); int high = bcs.getIntUnchecked(alignedBci + 2 * 4); if (low > high) { throw generatorError("low must be less than or equal to high in tableswitch"); } keys = high - low + 1; if (keys < 0) { throw generatorError("too many keys in tableswitch"); } delta = 1; } else { keys = bcs.getIntUnchecked(alignedBci + 4); if (keys < 0) { throw generatorError("number of keys in lookupswitch less than 0"); } delta = 2; for (int i = 0; i < (keys - 1); i++) { int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4); int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4); if (this_key >= next_key) { throw generatorError("Bad lookupswitch instruction"); } } } int target = bci + defaultOffset; checkJumpTarget(currentFrame, target); for (int i = 0; i < keys; i++) { target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4); checkJumpTarget(currentFrame, target); } } private void processFieldInstructions(RawBytecodeHelper bcs) { var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType()); switch (bcs.opcode()) { case GETSTATIC -> currentFrame.pushStack(desc); case PUTSTATIC -> { currentFrame.popStack(); if (Util.isDoubleSlot(desc)) currentFrame.popStack(); } case GETFIELD -> { currentFrame.popStack(); currentFrame.pushStack(desc); } case PUTFIELD -> { currentFrame.popStack(); currentFrame.popStack(); if (Util.isDoubleSlot(desc)) currentFrame.popStack(); } default -> throw new AssertionError("Should not reach here"); } } private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) { int index = bcs.getIndexU2(); int opcode = bcs.opcode(); var nameAndType = opcode == INVOKEDYNAMIC ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType() : cp.entryByIndex(index, MemberRefEntry.class).nameAndType(); String invokeMethodName = nameAndType.name().stringValue(); var mDesc = Util.methodTypeSymbol(nameAndType); int bci = bcs.bci(); currentFrame.decStack(Util.parameterSlots(mDesc)); if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { Type type = currentFrame.popStack(); if (type == Type.UNITIALIZED_THIS_TYPE) { if (inTryBlock) { processExceptionHandlerTargets(bci, true); } currentFrame.initializeObject(type, thisType); thisUninit = true; } else if (type.tag == ITEM_UNINITIALIZED) { int new_offset = type.bci; int new_class_index = bcs.getU2(new_offset + 1); Type new_class_type = cpIndexToType(new_class_index, cp); if (inTryBlock) { processExceptionHandlerTargets(bci, thisUninit); } currentFrame.initializeObject(type, new_class_type); } else { throw generatorError("Bad operand type when invoking "); } } else { currentFrame.popStack(); } } currentFrame.pushStack(mDesc.returnType()); return thisUninit; } private Type getNewarrayType(int index) { if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index)); return ARRAY_FROM_BASIC_TYPE[index]; } private void processAnewarray(int index) { currentFrame.popStack(); currentFrame.pushStack(cpIndexToType(index, cp).toArray()); } /** * {@return the generator error with attached details} * @param msg error message */ private IllegalArgumentException generatorError(String msg) { return generatorError(msg, currentFrame.offset); } /** * {@return the generator error with attached details} * @param msg error message * @param offset bytecode offset where the error occurred */ private IllegalArgumentException generatorError(String msg, int offset) { var sb = new StringBuilder("%s at bytecode offset %d of method %s(%s)".formatted( msg, offset, methodName, methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")))); Util.dumpMethod(cp, thisType.sym(), methodName, methodDesc, isStatic ? ACC_STATIC : 0, bytecode, sb::append); return new IllegalArgumentException(sb.toString()); } /** * Performs detection of mandatory stack map frames offsets * in a single bytecode traversing pass * @return java.lang.BitSet of detected frames offsets */ private BitSet detectFrameOffsets() { var offsets = new BitSet() { @Override public void set(int i) { Objects.checkIndex(i, bytecode.length()); super.set(i); } }; var bcs = bytecode.start(); boolean no_control_flow = false; int opcode, bci = 0; while (bcs.next()) try { opcode = bcs.opcode(); bci = bcs.bci(); if (no_control_flow) { offsets.set(bci); } no_control_flow = switch (opcode) { case GOTO -> { offsets.set(bcs.dest()); yield true; } case GOTO_W -> { offsets.set(bcs.destW()); yield true; } 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 TABLESWITCH, LOOKUPSWITCH -> { int aligned_bci = RawBytecodeHelper.align(bci + 1); int default_ofset = bcs.getIntUnchecked(aligned_bci); int keys, delta; if (bcs.opcode() == TABLESWITCH) { int low = bcs.getIntUnchecked(aligned_bci + 4); int high = bcs.getIntUnchecked(aligned_bci + 2 * 4); keys = high - low + 1; delta = 1; } else { keys = bcs.getIntUnchecked(aligned_bci + 4); delta = 2; } offsets.set(bci + default_ofset); for (int i = 0; i < keys; i++) { offsets.set(bci + bcs.getIntUnchecked(aligned_bci + (3 + i * delta) * 4)); } yield true; } case IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ATHROW -> true; default -> false; }; } catch (IllegalArgumentException iae) { throw generatorError("Detected branch target out of bytecode range", bci); } for (var exhandler : rawHandlers) try { offsets.set(exhandler.handler()); } catch (IllegalArgumentException iae) { if (!filterDeadLabels) throw generatorError("Detected exception handler out of bytecode range"); } return offsets; } private final class Frame { int offset; int localsSize, stackSize; int flags; int frameMaxStack = 0, frameMaxLocals = 0; boolean dirty = false; boolean localsChanged = false; private final ClassHierarchyImpl classHierarchy; private Type[] locals, stack; Frame(ClassHierarchyImpl classHierarchy) { this(-1, 0, 0, 0, null, null, classHierarchy); } Frame(int offset, ClassHierarchyImpl classHierarchy) { this(offset, -1, 0, 0, null, null, classHierarchy); } Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) { this.offset = offset; this.localsSize = locals_size; this.stackSize = stack_size; this.flags = flags; this.locals = locals; this.stack = stack; this.classHierarchy = classHierarchy; } @Override public String toString() { return (dirty ? "frame* @" : "frame @") + offset + " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) + " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)); } Frame pushStack(ClassDesc desc) { return switch (desc.descriptorString().charAt(0)) { case 'J' -> pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case 'D' -> pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case 'I', 'Z', 'B', 'C', 'S' -> pushStack(Type.INTEGER_TYPE); case 'F' -> pushStack(Type.FLOAT_TYPE); case 'V' -> this; default -> pushStack(Type.referenceType(desc)); }; } Frame pushStack(Type type) { checkStack(stackSize); stack[stackSize++] = type; return this; } Frame pushStack(Type type1, Type type2) { checkStack(stackSize + 1); stack[stackSize++] = type1; stack[stackSize++] = type2; return this; } Type popStack() { if (stackSize < 1) throw generatorError("Operand stack underflow"); return stack[--stackSize]; } Frame decStack(int size) { stackSize -= size; if (stackSize < 0) throw generatorError("Operand stack underflow"); return this; } Frame frameInExceptionHandler(int flags, Type excType) { return new Frame(offset, flags, localsSize, 1, locals, new Type[] {excType}, classHierarchy); } void initializeObject(Type old_object, Type new_object) { int i; for (i = 0; i < localsSize; i++) { if (locals[i].equals(old_object)) { locals[i] = new_object; localsChanged = true; } } for (i = 0; i < stackSize; i++) { if (stack[i].equals(old_object)) { stack[i] = new_object; } } if (old_object == Type.UNITIALIZED_THIS_TYPE) { flags = 0; } } Frame checkLocal(int index) { if (index >= frameMaxLocals) frameMaxLocals = index + 1; if (locals == null) { locals = new Type[index + FRAME_DEFAULT_CAPACITY]; Arrays.fill(locals, Type.TOP_TYPE); } else if (index >= locals.length) { int current = locals.length; locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY); Arrays.fill(locals, current, locals.length, Type.TOP_TYPE); } return this; } private void checkStack(int index) { if (index >= frameMaxStack) frameMaxStack = index + 1; if (stack == null) { stack = new Type[index + FRAME_DEFAULT_CAPACITY]; Arrays.fill(stack, Type.TOP_TYPE); } else if (index >= stack.length) { int current = stack.length; stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY); Arrays.fill(stack, current, stack.length, Type.TOP_TYPE); } } private void setLocalRawInternal(int index, Type type) { checkLocal(index); localsChanged |= !type.equals(locals[index]); locals[index] = type; } void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { int localsSize = 0; // Pre-emptively create a locals array that encompass all parameter slots checkLocal(methodDesc.parameterCount() + (isStatic ? -1 : 0)); if (!isStatic) { localsSize++; if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) { setLocal(0, Type.UNITIALIZED_THIS_TYPE); flags |= FLAG_THIS_UNINIT; } else { setLocalRawInternal(0, thisKlass); } } for (int i = 0; i < methodDesc.parameterCount(); i++) { var desc = methodDesc.parameterType(i); if (!desc.isPrimitive()) { setLocalRawInternal(localsSize++, Type.referenceType(desc)); } else switch (desc.descriptorString().charAt(0)) { case 'J' -> { setLocalRawInternal(localsSize++, Type.LONG_TYPE); setLocalRawInternal(localsSize++, Type.LONG2_TYPE); } case 'D' -> { setLocalRawInternal(localsSize++, Type.DOUBLE_TYPE); setLocalRawInternal(localsSize++, Type.DOUBLE2_TYPE); } case 'I', 'Z', 'B', 'C', 'S' -> setLocalRawInternal(localsSize++, Type.INTEGER_TYPE); case 'F' -> setLocalRawInternal(localsSize++, Type.FLOAT_TYPE); default -> throw new AssertionError("Should not reach here"); } } this.localsSize = localsSize; } void copyFrom(Frame src) { if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE); localsSize = src.localsSize; checkLocal(src.localsSize - 1); if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize); if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE); stackSize = src.stackSize; checkStack(src.stackSize - 1); if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize); flags = src.flags; localsChanged = true; } void checkAssignableTo(Frame target) { if (target.flags == -1) { target.locals = locals == null ? null : Arrays.copyOf(locals, localsSize); target.localsSize = localsSize; target.stack = stack == null ? null : Arrays.copyOf(stack, stackSize); target.stackSize = stackSize; target.flags = flags; target.dirty = true; } else { if (target.localsSize > localsSize) { target.localsSize = localsSize; target.dirty = true; } for (int i = 0; i < target.localsSize; i++) { merge(locals[i], target.locals, i, target); } if (stackSize != target.stackSize) { throw generatorError("Stack size mismatch"); } for (int i = 0; i < target.stackSize; i++) { if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) { throw generatorError("Stack content mismatch"); } } } } private Type getLocalRawInternal(int index) { checkLocal(index); return locals[index]; } Type getLocal(int index) { Type ret = getLocalRawInternal(index); if (index >= localsSize) { localsSize = index + 1; } return ret; } void setLocal(int index, Type type) { Type old = getLocalRawInternal(index); if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { setLocalRawInternal(index + 1, Type.TOP_TYPE); } if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { setLocalRawInternal(index - 1, Type.TOP_TYPE); } setLocalRawInternal(index, type); if (index >= localsSize) { localsSize = index + 1; } } void setLocal2(int index, Type type1, Type type2) { Type old = getLocalRawInternal(index + 1); if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { setLocalRawInternal(index + 2, Type.TOP_TYPE); } old = getLocalRawInternal(index); if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { setLocalRawInternal(index - 1, Type.TOP_TYPE); } setLocalRawInternal(index, type1); setLocalRawInternal(index + 1, type2); if (index >= localsSize - 1) { localsSize = index + 2; } } private Type merge(Type me, Type[] toTypes, int i, Frame target) { var to = toTypes[i]; var newTo = to.mergeFrom(me, classHierarchy); if (to != newTo && !to.equals(newTo)) { toTypes[i] = newTo; target.dirty = true; } return newTo; } private static int trimAndCompress(Type[] types, int count) { while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--; int compressed = 0; for (int i = 0; i < count; i++) { if (!types[i].isCategory2_2nd()) { types[compressed++] = types[i]; } } return compressed; } void trimAndCompress() { localsSize = trimAndCompress(locals, localsSize); stackSize = trimAndCompress(stack, stackSize); } private static boolean equals(Type[] l1, Type[] l2, int commonSize) { if (l1 == null || l2 == null) return commonSize == 0; return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize); } void writeTo(BufWriter out, Frame prevFrame, ConstantPoolBuilder cp) { int offsetDelta = offset - prevFrame.offset - 1; if (stackSize == 0) { int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize; int diffLocalsSize = localsSize - prevFrame.localsSize; if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) { if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame out.writeU1(offsetDelta); } else { //chop, same extended or append frame out.writeU1(251 + diffLocalsSize); out.writeU2(offsetDelta); for (int i=commonLocalsSize; i from == INTEGER_TYPE ? this : TOP_TYPE; default -> isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; }; } } Type mergeComponentFrom(Type from, ClassHierarchyImpl context) { if (this == TOP_TYPE || this == from || equals(from)) { return this; } else { return switch (tag) { case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT -> TOP_TYPE; default -> isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; }; } } private static final ClassDesc CD_Cloneable = Cloneable.class.describeConstable().orElseThrow(); private static final ClassDesc CD_Serializable = java.io.Serializable.class.describeConstable().orElseThrow(); private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { if (from == NULL_TYPE) { return this; } else if (this == NULL_TYPE) { return from; } else if (sym.equals(from.sym)) { return this; } else if (isObject()) { if (CD_Object.equals(sym)) { return this; } if (context.isInterface(sym)) { if (!from.isArray() || CD_Cloneable.equals(sym) || CD_Serializable.equals(sym)) { return this; } } else if (from.isObject()) { var anc = context.commonAncestor(sym, from.sym); return anc == null ? this : Type.referenceType(anc); } } else if (isArray() && from.isArray()) { Type compThis = getComponent(); Type compFrom = from.getComponent(); if (compThis != TOP_TYPE && compFrom != TOP_TYPE) { return compThis.mergeComponentFrom(compFrom, context).toArray(); } } return OBJECT_TYPE; } Type toArray() { return switch (tag) { case ITEM_BOOLEAN -> BOOLEAN_ARRAY_TYPE; case ITEM_BYTE -> BYTE_ARRAY_TYPE; case ITEM_CHAR -> CHAR_ARRAY_TYPE; case ITEM_SHORT -> SHORT_ARRAY_TYPE; case ITEM_INTEGER -> INT_ARRAY_TYPE; case ITEM_LONG -> LONG_ARRAY_TYPE; case ITEM_FLOAT -> FLOAT_ARRAY_TYPE; case ITEM_DOUBLE -> DOUBLE_ARRAY_TYPE; case ITEM_OBJECT -> Type.referenceType(sym.arrayType()); default -> OBJECT_TYPE; }; } Type getComponent() { if (isArray()) { var comp = sym.componentType(); if (comp.isPrimitive()) { return switch (comp.descriptorString().charAt(0)) { case 'Z' -> Type.BOOLEAN_TYPE; case 'B' -> Type.BYTE_TYPE; case 'C' -> Type.CHAR_TYPE; case 'S' -> Type.SHORT_TYPE; case 'I' -> Type.INTEGER_TYPE; case 'J' -> Type.LONG_TYPE; case 'F' -> Type.FLOAT_TYPE; case 'D' -> Type.DOUBLE_TYPE; default -> Type.TOP_TYPE; }; } return Type.referenceType(comp); } return Type.TOP_TYPE; } void writeTo(BufWriter bw, ConstantPoolBuilder cp) { bw.writeU1(tag); switch (tag) { case ITEM_OBJECT -> bw.writeU2(cp.classEntry(sym).index()); case ITEM_UNINITIALIZED -> bw.writeU2(bci); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy