< prev index next >

src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java

Print this page

   1 /*
   2  * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.  Oracle designates this
   9  * particular file as subject to the "Classpath" exception as provided
  10  * by Oracle in the LICENSE file that accompanied this code.
  11  *
  12  * This code is distributed in the hope that it will be useful, but WITHOUT
  13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  15  * version 2 for more details (a copy is included in the LICENSE file that
  16  * accompanied this code).
  17  *
  18  * You should have received a copy of the GNU General Public License version
  19  * 2 along with this work; if not, write to the Free Software Foundation,
  20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  21  *
  22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  23  * or visit www.oracle.com if you need additional information or have any
  24  * questions.
  25  *
  26  */
  27 package jdk.internal.classfile.impl;
  28 
  29 import java.lang.classfile.Attribute;
  30 import java.lang.classfile.Attributes;
  31 import java.lang.classfile.Label;
  32 import java.lang.classfile.attribute.StackMapTableAttribute;
  33 import java.lang.classfile.constantpool.ClassEntry;
  34 import java.lang.classfile.constantpool.ConstantDynamicEntry;
  35 import java.lang.classfile.constantpool.ConstantPoolBuilder;
  36 import java.lang.classfile.constantpool.InvokeDynamicEntry;
  37 import java.lang.classfile.constantpool.MemberRefEntry;
  38 import java.lang.classfile.constantpool.Utf8Entry;
  39 import java.lang.constant.ClassDesc;
  40 import java.lang.constant.MethodTypeDesc;
  41 import java.util.ArrayList;
  42 import java.util.Arrays;
  43 import java.util.List;
  44 import java.util.Objects;
  45 import java.util.stream.Collectors;
  46 

  47 import jdk.internal.constant.ClassOrInterfaceDescImpl;
  48 import jdk.internal.util.Preconditions;
  49 
  50 import static java.lang.classfile.ClassFile.*;
  51 import static java.lang.classfile.constantpool.PoolEntry.*;
  52 import static java.lang.constant.ConstantDescs.*;
  53 import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
  54 
  55 /**
  56  * StackMapGenerator is responsible for stack map frames generation.
  57  * <p>
  58  * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.
  59  * <p>
  60  * The {@linkplain #generate() frames computation} consists of following steps:
  61  * <ol>
  62  * <li>{@linkplain #detectFrames() Detection} of mandatory stack map frames:<ul>
  63  *      <li>Mandatory stack map frame include all jump and switch instructions targets,
  64  *          offsets immediately following {@linkplain #noControlFlow(int) "no control flow"}
  65  *          and all exception table handlers.
  66  *      <li>Detection is performed in a single fast pass through the bytecode,

 140  * Focus of the whole algorithm is on high performance and low memory footprint:<ul>
 141  *      <li>It does not produce, collect nor visit any complex intermediate structures
 142  *          <i>(beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}).</i>
 143  *      <li>It works with only minimal mandatory stack map frames.
 144  *      <li>It does not spend time on any non-essential verifications.
 145  * </ul>
 146  */
 147 
 148 public final class StackMapGenerator {
 149 
 150     static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) {
 151         return new StackMapGenerator(
 152                 dcb,
 153                 buf.thisClass().asSymbol(),
 154                 dcb.methodInfo.methodName().stringValue(),
 155                 dcb.methodInfo.methodTypeSymbol(),
 156                 (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0,
 157                 dcb.bytecodesBufWriter.bytecodeView(),
 158                 dcb.constantPool,
 159                 dcb.context,

 160                 dcb.handlers);
 161     }
 162 
 163     private static final String OBJECT_INITIALIZER_NAME = "<init>";
 164     private static final int FLAG_THIS_UNINIT = 0x01;
 165     private static final int FRAME_DEFAULT_CAPACITY = 10;
 166     private static final int T_BOOLEAN = 4, T_LONG = 11;
 167     private static final Frame[] EMPTY_FRAME_ARRAY = {};
 168 
 169     public static final int
 170             ITEM_TOP = 0,
 171             ITEM_INTEGER = 1,
 172             ITEM_FLOAT = 2,
 173             ITEM_DOUBLE = 3,
 174             ITEM_LONG = 4,
 175             ITEM_NULL = 5,
 176             ITEM_UNINITIALIZED_THIS = 6,
 177             ITEM_OBJECT = 7,
 178             ITEM_UNINITIALIZED = 8,
 179             ITEM_BOOLEAN = 9,
 180             ITEM_BYTE = 10,
 181             ITEM_SHORT = 11,
 182             ITEM_CHAR = 12,
 183             ITEM_LONG_2ND = 13,
 184             ITEM_DOUBLE_2ND = 14,
 185             ITEM_BOGUS = -1;
 186 
 187     // Ranges represented by these constants are inclusive on both ends
 188     public static final int
 189             SAME_FRAME_END = 63,
 190             SAME_LOCALS_1_STACK_ITEM_FRAME_START = 64,
 191             SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127,
 192             RESERVED_END = 246,

 193             SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
 194             CHOP_FRAME_START = 248,
 195             CHOP_FRAME_END = 250,
 196             SAME_FRAME_EXTENDED = 251,
 197             APPEND_FRAME_START = 252,
 198             APPEND_FRAME_END = 254,
 199             FULL_FRAME = 255;
 200 
 201     private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
 202         Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
 203         Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
 204 
 205     static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
 206 
 207     private final Type thisType;
 208     private final String methodName;
 209     private final MethodTypeDesc methodDesc;
 210     private final RawBytecodeHelper.CodeRange bytecode;
 211     private final SplitConstantPool cp;
 212     private final boolean isStatic;
 213     private final LabelContext labelContext;
 214     private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
 215     private final List<RawExceptionCatch> rawHandlers;
 216     private final ClassHierarchyImpl classHierarchy;

 217     private final boolean patchDeadCode;
 218     private final boolean filterDeadLabels;
 219     private Frame[] frames = EMPTY_FRAME_ARRAY;
 220     private int framesCount = 0;
 221     private final Frame currentFrame;
 222     private int maxStack, maxLocals;
 223 
 224     /**
 225      * Primary constructor of the <code>Generator</code> class.
 226      * New <code>Generator</code> instance must be created for each individual class/method.
 227      * Instance contains only immutable results, all the calculations are processed during instance construction.
 228      *
 229      * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
 230      * labels to bytecode offsets (or vice versa)
 231      * @param thisClass class to generate stack maps for
 232      * @param methodName method name to generate stack maps for
 233      * @param methodDesc method descriptor to generate stack maps for
 234      * @param isStatic information whether the method is static
 235      * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects  and patches dead code
 236      * @param cp R/W <code>ConstantPoolBuilder</code> instance used to resolve all involved CP entries and also generate new entries referenced from the generated stack maps
 237      * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
 238      * and also to be altered when dead code is detected and must be excluded from exception handlers
 239      */
 240     public StackMapGenerator(LabelContext labelContext,
 241                      ClassDesc thisClass,
 242                      String methodName,
 243                      MethodTypeDesc methodDesc,
 244                      boolean isStatic,
 245                      RawBytecodeHelper.CodeRange bytecode,
 246                      SplitConstantPool cp,
 247                      ClassFileImpl context,

 248                      List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
 249         this.thisType = Type.referenceType(thisClass);
 250         this.methodName = methodName;
 251         this.methodDesc = methodDesc;
 252         this.isStatic = isStatic;
 253         this.bytecode = bytecode;
 254         this.cp = cp;
 255         this.labelContext = labelContext;
 256         this.handlers = handlers;
 257         this.rawHandlers = new ArrayList<>(handlers.size());
 258         this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
 259         this.patchDeadCode = context.patchDeadCode();
 260         this.filterDeadLabels = context.dropDeadLabels();
 261         this.currentFrame = new Frame(classHierarchy);





 262         generate();
 263     }
 264 
 265     /**
 266      * Calculated maximum number of the locals required
 267      * @return maximum number of the locals required
 268      */
 269     public int maxLocals() {
 270         return maxLocals;
 271     }
 272 
 273     /**
 274      * Calculated maximum stack size required
 275      * @return maximum stack size required
 276      */
 277     public int maxStack() {
 278         return maxStack;
 279     }
 280 
 281     private Frame getFrame(int offset) {

 390             } else {
 391                 //split
 392                 Label newStart = labelContext.newLabel();
 393                 labelContext.setLabelTarget(newStart, rangeEnd);
 394                 Label newEnd = labelContext.newLabel();
 395                 labelContext.setLabelTarget(newEnd, rangeStart);
 396                 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
 397                 it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
 398             }
 399         }
 400     }
 401 
 402     /**
 403      * Getter of the generated <code>StackMapTableAttribute</code> or null if stack map is empty
 404      * @return <code>StackMapTableAttribute</code> or null if stack map is empty
 405      */
 406     public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
 407         return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
 408             @Override
 409             public void writeBody(BufWriterImpl b) {



 410                 b.writeU2(framesCount);
 411                 Frame prevFrame =  new Frame(classHierarchy);
 412                 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
 413                 prevFrame.trimAndCompress();
 414                 for (int i = 0; i < framesCount; i++) {
 415                     var fr = frames[i];
 416                     fr.trimAndCompress();
 417                     fr.writeTo(b, prevFrame, cp);
 418                     prevFrame = fr;
 419                 }
 420             }
 421 
 422             @Override
 423             public Utf8Entry attributeName() {
 424                 return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
 425             }
 426         };
 427     }
 428 
 429     private static Type cpIndexToType(int index, ConstantPoolBuilder cp) {
 430         return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol());
 431     }
 432 
 433     private void processMethod() {
 434         var frames = this.frames;
 435         var currentFrame = this.currentFrame;
 436         currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
 437         currentFrame.stackSize = 0;
 438         currentFrame.flags = 0;
 439         currentFrame.offset = -1;
 440         int stackmapIndex = 0;
 441         var bcs = bytecode.start();
 442         boolean ncf = false;
 443         while (bcs.next()) {
 444             currentFrame.offset = bcs.bci();
 445             if (stackmapIndex < framesCount) {
 446                 int thisOffset = frames[stackmapIndex].offset;
 447                 if (ncf && thisOffset > bcs.bci()) {
 448                     throw generatorError("Expecting a stack map frame");
 449                 }
 450                 if (thisOffset == bcs.bci()) {
 451                     Frame nextFrame = frames[stackmapIndex++];
 452                     if (!ncf) {
 453                         currentFrame.checkAssignableTo(nextFrame);
 454                     }
 455                     while (!nextFrame.dirty) { //skip unmatched frames
 456                         if (stackmapIndex == framesCount) return; //skip the rest of this round
 457                         nextFrame = frames[stackmapIndex++];
 458                     }

 461                     currentFrame.offset = bcs.bci();
 462                     currentFrame.copyFrom(nextFrame);
 463                     nextFrame.dirty = false;
 464                 } else if (thisOffset < bcs.bci()) {
 465                     throw generatorError("Bad stack map offset");
 466                 }
 467             } else if (ncf) {
 468                 throw generatorError("Expecting a stack map frame");
 469             }
 470             ncf = processBlock(bcs);
 471         }
 472     }
 473 
 474     private boolean processBlock(RawBytecodeHelper bcs) {
 475         int opcode = bcs.opcode();
 476         boolean ncf = false;
 477         boolean this_uninit = false;
 478         boolean verified_exc_handlers = false;
 479         int bci = bcs.bci();
 480         Type type1, type2, type3, type4;
 481         if (RawBytecodeHelper.isStoreIntoLocal(opcode) && bci >= exMin && bci < exMax) {

 482             processExceptionHandlerTargets(bci, this_uninit);
 483             verified_exc_handlers = true;
 484         }
 485         switch (opcode) {
 486             case NOP -> {}
 487             case RETURN -> {
 488                 ncf = true;
 489             }
 490             case ACONST_NULL ->
 491                 currentFrame.pushStack(Type.NULL_TYPE);
 492             case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH ->
 493                 currentFrame.pushStack(Type.INTEGER_TYPE);
 494             case LCONST_0, LCONST_1 ->
 495                 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 496             case FCONST_0, FCONST_1, FCONST_2 ->
 497                 currentFrame.pushStack(Type.FLOAT_TYPE);
 498             case DCONST_0, DCONST_1 ->
 499                 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 500             case LDC ->
 501                 processLdc(bcs.getIndexU1());

 684                 type1 = cpIndexToType(bcs.getIndexU2(), cp);
 685                 int dim = bcs.getU1Unchecked(bcs.bci() + 3);
 686                 for (int i = 0; i < dim; i++) {
 687                     currentFrame.popStack();
 688                 }
 689                 currentFrame.pushStack(type1);
 690             }
 691             case JSR, JSR_W, RET ->
 692                 throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0");
 693             default ->
 694                 throw generatorError(String.format("Bad instruction: %02x", opcode));
 695         }
 696         if (!verified_exc_handlers && bci >= exMin && bci < exMax) {
 697             processExceptionHandlerTargets(bci, this_uninit);
 698         }
 699         return ncf;
 700     }
 701 
 702     private void processExceptionHandlerTargets(int bci, boolean this_uninit) {
 703         for (var ex : rawHandlers) {
 704             if (bci == ex.start || (currentFrame.localsChanged && bci > ex.start && bci < ex.end)) {
 705                 int flags = currentFrame.flags;
 706                 if (this_uninit) flags |= FLAG_THIS_UNINIT;
 707                 Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType);
 708                 checkJumpTarget(newFrame, ex.handler);
 709             }
 710         }
 711         currentFrame.localsChanged = false;
 712     }
 713 
 714     private void processLdc(int index) {
 715         switch (cp.entryByIndex(index).tag()) {
 716             case TAG_UTF8 ->
 717                 currentFrame.pushStack(Type.OBJECT_TYPE);
 718             case TAG_STRING ->
 719                 currentFrame.pushStack(Type.STRING_TYPE);
 720             case TAG_CLASS ->
 721                 currentFrame.pushStack(Type.CLASS_TYPE);
 722             case TAG_INTEGER ->
 723                 currentFrame.pushStack(Type.INTEGER_TYPE);
 724             case TAG_FLOAT ->
 725                 currentFrame.pushStack(Type.FLOAT_TYPE);
 726             case TAG_DOUBLE ->
 727                 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 728             case TAG_LONG ->
 729                 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 730             case TAG_METHOD_HANDLE ->
 731                 currentFrame.pushStack(Type.METHOD_HANDLE_TYPE);

 761                 throw generatorError("number of keys in lookupswitch less than 0");
 762             }
 763             delta = 2;
 764             for (int i = 0; i < (keys - 1); i++) {
 765                 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
 766                 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
 767                 if (this_key >= next_key) {
 768                     throw generatorError("Bad lookupswitch instruction");
 769                 }
 770             }
 771         }
 772         int target = bci + defaultOffset;
 773         checkJumpTarget(currentFrame, target);
 774         for (int i = 0; i < keys; i++) {
 775             target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
 776             checkJumpTarget(currentFrame, target);
 777         }
 778     }
 779 
 780     private void processFieldInstructions(RawBytecodeHelper bcs) {
 781         var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).type());

 782         var currentFrame = this.currentFrame;
 783         switch (bcs.opcode()) {
 784             case GETSTATIC ->
 785                 currentFrame.pushStack(desc);
 786             case PUTSTATIC -> {
 787                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
 788             }
 789             case GETFIELD -> {
 790                 currentFrame.decStack(1);
 791                 currentFrame.pushStack(desc);
 792             }
 793             case PUTFIELD -> {



 794                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
 795             }
 796             default -> throw new AssertionError("Should not reach here");
 797         }
 798     }
 799 
 800     private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) {
 801         int index = bcs.getIndexU2();
 802         int opcode = bcs.opcode();
 803         var nameAndType = opcode == INVOKEDYNAMIC
 804                 ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType()
 805                 : cp.entryByIndex(index, MemberRefEntry.class).nameAndType();
 806         var mDesc = Util.methodTypeSymbol(nameAndType.type());
 807         int bci = bcs.bci();
 808         var currentFrame = this.currentFrame;
 809         currentFrame.decStack(Util.parameterSlots(mDesc));
 810         if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) {
 811             if (nameAndType.name().equalsString(OBJECT_INITIALIZER_NAME)) {
 812                 Type type = currentFrame.popStack();
 813                 if (type == Type.UNITIALIZED_THIS_TYPE) {
 814                     if (inTryBlock) {
 815                         processExceptionHandlerTargets(bci, true);
 816                     }





 817                     currentFrame.initializeObject(type, thisType);


 818                     thisUninit = true;
 819                 } else if (type.tag == ITEM_UNINITIALIZED) {
 820                     Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
 821                     if (inTryBlock) {
 822                         processExceptionHandlerTargets(bci, thisUninit);
 823                     }
 824                     currentFrame.initializeObject(type, new_class_type);
 825                 } else {
 826                     throw generatorError("Bad operand type when invoking <init>");
 827                 }
 828             } else {
 829                 currentFrame.decStack(1);
 830             }
 831         }
 832         currentFrame.pushStack(mDesc.returnType());
 833         return thisUninit;
 834     }
 835 
 836     private Type getNewarrayType(int index) {
 837         if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));

 940                 return;
 941             }
 942             if (frameOffset > offset) {
 943                 break;
 944             }
 945         }
 946         if (framesCount >= frames.length) {
 947             int newCapacity = framesCount + 8;
 948             this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
 949         }
 950         if (i != framesCount) {
 951             System.arraycopy(frames, i, frames, i + 1, framesCount - i);
 952         }
 953         frames[i] = new Frame(offset, classHierarchy);
 954         this.framesCount = framesCount + 1;
 955     }
 956 
 957     private final class Frame {
 958 
 959         int offset;
 960         int localsSize, stackSize;
 961         int flags;
 962         int frameMaxStack = 0, frameMaxLocals = 0;
 963         boolean dirty = false;
 964         boolean localsChanged = false;
 965 
 966         private final ClassHierarchyImpl classHierarchy;
 967 
 968         private Type[] locals, stack;

 969 
 970         Frame(ClassHierarchyImpl classHierarchy) {
 971             this(-1, 0, 0, 0, null, null, classHierarchy);
 972         }
 973 
 974         Frame(int offset, ClassHierarchyImpl classHierarchy) {
 975             this(offset, -1, 0, 0, null, null, classHierarchy);
 976         }
 977 
 978         Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) {
 979             this.offset = offset;
 980             this.localsSize = locals_size;
 981             this.stackSize = stack_size;

 982             this.flags = flags;
 983             this.locals = locals;
 984             this.stack = stack;

 985             this.classHierarchy = classHierarchy;
 986         }
 987 
 988         @Override
 989         public String toString() {
 990             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));



 991         }
 992 
 993         Frame pushStack(ClassDesc desc) {
 994             if (desc == CD_long)   return pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 995             if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 996             return desc == CD_void ? this
 997                     : pushStack(
 998                     desc.isPrimitive()
 999                             ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
1000                             : Type.referenceType(desc));
1001         }
1002 
1003         Frame pushStack(Type type) {
1004             checkStack(stackSize);
1005             stack[stackSize++] = type;
1006             return this;
1007         }
1008 
1009         Frame pushStack(Type type1, Type type2) {
1010             checkStack(stackSize + 1);
1011             stack[stackSize++] = type1;
1012             stack[stackSize++] = type2;
1013             return this;
1014         }
1015 
1016         Type popStack() {
1017             if (stackSize < 1) throw generatorError("Operand stack underflow");
1018             return stack[--stackSize];
1019         }
1020 
1021         Frame decStack(int size) {
1022             stackSize -= size;
1023             if (stackSize < 0) throw generatorError("Operand stack underflow");
1024             return this;
1025         }
1026 
1027         Frame frameInExceptionHandler(int flags, Type excType) {
1028             return new Frame(offset, flags, localsSize, 1, locals, new Type[] {excType}, classHierarchy);

1029         }
1030 
1031         void initializeObject(Type old_object, Type new_object) {
1032             int i;
1033             for (i = 0; i < localsSize; i++) {
1034                 if (locals[i].equals(old_object)) {
1035                     locals[i] = new_object;
1036                     localsChanged = true;
1037                 }
1038             }
1039             for (i = 0; i < stackSize; i++) {
1040                 if (stack[i].equals(old_object)) {
1041                     stack[i] = new_object;
1042                 }
1043             }
1044             if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1045                 flags = 0;

1046             }
1047         }
1048 
1049         Frame checkLocal(int index) {
1050             if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1051             if (locals == null) {
1052                 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1053                 Arrays.fill(locals, Type.TOP_TYPE);
1054             } else if (index >= locals.length) {
1055                 int current = locals.length;
1056                 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1057                 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1058             }
1059             return this;
1060         }
1061 




















1062         private void checkStack(int index) {
1063             if (index >= frameMaxStack) frameMaxStack = index + 1;
1064             if (stack == null) {
1065                 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1066                 Arrays.fill(stack, Type.TOP_TYPE);
1067             } else if (index >= stack.length) {
1068                 int current = stack.length;
1069                 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1070                 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1071             }
1072         }
1073 
1074         private void setLocalRawInternal(int index, Type type) {
1075             checkLocal(index);
1076             localsChanged |= !type.equals(locals[index]);
1077             locals[index] = type;
1078         }
1079 
1080         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) {
1081             int localsSize = 0;
1082             // Pre-emptively create a locals array that encompass all parameter slots
1083             checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1084             Type type;
1085             Type[] locals = this.locals;
1086             if (!isStatic) {
1087                 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {



1088                     type = Type.UNITIALIZED_THIS_TYPE;
1089                     flags |= FLAG_THIS_UNINIT;
1090                 } else {


1091                     type = thisKlass;

1092                 }
1093                 locals[localsSize++] = type;
1094             }
1095             for (int i = 0; i < methodDesc.parameterCount(); i++) {
1096                 var desc = methodDesc.parameterType(i);
1097                 if (desc == CD_long) {
1098                     locals[localsSize    ] = Type.LONG_TYPE;
1099                     locals[localsSize + 1] = Type.LONG2_TYPE;
1100                     localsSize += 2;
1101                 } else if (desc == CD_double) {
1102                     locals[localsSize    ] = Type.DOUBLE_TYPE;
1103                     locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1104                     localsSize += 2;
1105                 } else {
1106                     if (!desc.isPrimitive()) {
1107                         type = Type.referenceType(desc);
1108                     } else if (desc == CD_float) {
1109                         type = Type.FLOAT_TYPE;
1110                     } else {
1111                         type = Type.INTEGER_TYPE;
1112                     }
1113                     locals[localsSize++] = type;
1114                 }
1115             }
1116             this.localsSize = localsSize;
1117         }
1118 
1119         void copyFrom(Frame src) {
1120             if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1121             localsSize = src.localsSize;
1122             checkLocal(src.localsSize - 1);
1123             if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1124             if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1125             stackSize = src.stackSize;
1126             checkStack(src.stackSize - 1);
1127             if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);


1128             flags = src.flags;
1129             localsChanged = true;
1130         }
1131 
1132         void checkAssignableTo(Frame target) {
1133             int localsSize = this.localsSize;
1134             int stackSize = this.stackSize;

1135             if (target.flags == -1) {
1136                 target.locals = locals == null ? null : locals.clone();
1137                 target.localsSize = localsSize;
1138                 if (stackSize > 0) {
1139                     target.stack = stack.clone();
1140                     target.stackSize = stackSize;
1141                 }


1142                 target.flags = flags;
1143                 target.dirty = true;
1144             } else {
1145                 if (target.localsSize > localsSize) {
1146                     target.localsSize = localsSize;
1147                     target.dirty = true;
1148                 }
1149                 for (int i = 0; i < target.localsSize; i++) {
1150                     merge(locals[i], target.locals, i, target);
1151                 }
1152                 if (stackSize != target.stackSize) {
1153                     throw generatorError("Stack size mismatch");
1154                 }
1155                 for (int i = 0; i < target.stackSize; i++) {
1156                     if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1157                         throw generatorError("Stack content mismatch");
1158                     }
1159                 }



1160             }
1161         }
1162 
1163         private Type getLocalRawInternal(int index) {
1164             checkLocal(index);
1165             return locals[index];
1166         }
1167 
1168         Type getLocal(int index) {
1169             Type ret = getLocalRawInternal(index);
1170             if (index >= localsSize) {
1171                 localsSize = index + 1;
1172             }
1173             return ret;
1174         }
1175 
1176         void setLocal(int index, Type type) {
1177             Type old = getLocalRawInternal(index);
1178             if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1179                 setLocalRawInternal(index + 1, Type.TOP_TYPE);

1196             if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1197                 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1198             }
1199             setLocalRawInternal(index, type1);
1200             setLocalRawInternal(index + 1, type2);
1201             if (index >= localsSize - 1) {
1202                 localsSize = index + 2;
1203             }
1204         }
1205 
1206         private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1207             var to = toTypes[i];
1208             var newTo = to.mergeFrom(me, classHierarchy);
1209             if (to != newTo && !to.equals(newTo)) {
1210                 toTypes[i] = newTo;
1211                 target.dirty = true;
1212             }
1213             return newTo;
1214         }
1215 













































1216         private static int trimAndCompress(Type[] types, int count) {
1217             while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1218             int compressed = 0;
1219             for (int i = 0; i < count; i++) {
1220                 if (!types[i].isCategory2_2nd()) {
1221                     if (compressed != i) {
1222                         types[compressed] = types[i];
1223                     }
1224                     compressed++;
1225                 }
1226             }
1227             return compressed;
1228         }
1229 
1230         void trimAndCompress() {
1231             localsSize = trimAndCompress(locals, localsSize);
1232             stackSize = trimAndCompress(stack, stackSize);
1233         }
1234 










1235         private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1236             if (l1 == null || l2 == null) return commonSize == 0;
1237             return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1238         }
1239 











1240         void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {









1241             int localsSize = this.localsSize;
1242             int stackSize = this.stackSize;
1243             int offsetDelta = offset - prevFrame.offset - 1;
1244             if (stackSize == 0) {
1245                 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1246                 int diffLocalsSize = localsSize - prevFrame.localsSize;
1247                 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1248                     if (diffLocalsSize == 0 && offsetDelta <= SAME_FRAME_END) { //same frame
1249                         out.writeU1(offsetDelta);
1250                     } else {   //chop, same extended or append frame
1251                         out.writeU1U2(SAME_FRAME_EXTENDED + diffLocalsSize, offsetDelta);
1252                         for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1253                     }
1254                     return;
1255                 }
1256             } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1257                 if (offsetDelta <= SAME_LOCALS_1_STACK_ITEM_FRAME_END  - SAME_LOCALS_1_STACK_ITEM_FRAME_START) {  //same locals 1 stack item frame
1258                     out.writeU1(SAME_LOCALS_1_STACK_ITEM_FRAME_START + offsetDelta);
1259                 } else {  //same locals 1 stack item extended frame
1260                     out.writeU1U2(SAME_LOCALS_1_STACK_ITEM_EXTENDED, offsetDelta);

   1 /*
   2  * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.  Oracle designates this
   9  * particular file as subject to the "Classpath" exception as provided
  10  * by Oracle in the LICENSE file that accompanied this code.
  11  *
  12  * This code is distributed in the hope that it will be useful, but WITHOUT
  13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  15  * version 2 for more details (a copy is included in the LICENSE file that
  16  * accompanied this code).
  17  *
  18  * You should have received a copy of the GNU General Public License version
  19  * 2 along with this work; if not, write to the Free Software Foundation,
  20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  21  *
  22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  23  * or visit www.oracle.com if you need additional information or have any
  24  * questions.
  25  *
  26  */
  27 package jdk.internal.classfile.impl;
  28 
  29 import java.lang.classfile.Attribute;
  30 import java.lang.classfile.Attributes;
  31 import java.lang.classfile.Label;
  32 import java.lang.classfile.attribute.StackMapTableAttribute;
  33 import java.lang.classfile.constantpool.*;





  34 import java.lang.constant.ClassDesc;
  35 import java.lang.constant.MethodTypeDesc;
  36 import java.util.ArrayList;
  37 import java.util.Arrays;
  38 import java.util.List;
  39 import java.util.Objects;
  40 import java.util.stream.Collectors;
  41 
  42 import jdk.internal.classfile.impl.WritableField.UnsetField;
  43 import jdk.internal.constant.ClassOrInterfaceDescImpl;
  44 import jdk.internal.util.Preconditions;
  45 
  46 import static java.lang.classfile.ClassFile.*;
  47 import static java.lang.classfile.constantpool.PoolEntry.*;
  48 import static java.lang.constant.ConstantDescs.*;
  49 import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
  50 
  51 /**
  52  * StackMapGenerator is responsible for stack map frames generation.
  53  * <p>
  54  * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.
  55  * <p>
  56  * The {@linkplain #generate() frames computation} consists of following steps:
  57  * <ol>
  58  * <li>{@linkplain #detectFrames() Detection} of mandatory stack map frames:<ul>
  59  *      <li>Mandatory stack map frame include all jump and switch instructions targets,
  60  *          offsets immediately following {@linkplain #noControlFlow(int) "no control flow"}
  61  *          and all exception table handlers.
  62  *      <li>Detection is performed in a single fast pass through the bytecode,

 136  * Focus of the whole algorithm is on high performance and low memory footprint:<ul>
 137  *      <li>It does not produce, collect nor visit any complex intermediate structures
 138  *          <i>(beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}).</i>
 139  *      <li>It works with only minimal mandatory stack map frames.
 140  *      <li>It does not spend time on any non-essential verifications.
 141  * </ul>
 142  */
 143 
 144 public final class StackMapGenerator {
 145 
 146     static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) {
 147         return new StackMapGenerator(
 148                 dcb,
 149                 buf.thisClass().asSymbol(),
 150                 dcb.methodInfo.methodName().stringValue(),
 151                 dcb.methodInfo.methodTypeSymbol(),
 152                 (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0,
 153                 dcb.bytecodesBufWriter.bytecodeView(),
 154                 dcb.constantPool,
 155                 dcb.context,
 156                 buf.getStrictInstanceFields(),
 157                 dcb.handlers);
 158     }
 159 
 160     private static final String OBJECT_INITIALIZER_NAME = "<init>";
 161     private static final int FLAG_THIS_UNINIT = 0x01;
 162     private static final int FRAME_DEFAULT_CAPACITY = 10;
 163     private static final int T_BOOLEAN = 4, T_LONG = 11;
 164     private static final Frame[] EMPTY_FRAME_ARRAY = {};
 165 
 166     public static final int
 167             ITEM_TOP = 0,
 168             ITEM_INTEGER = 1,
 169             ITEM_FLOAT = 2,
 170             ITEM_DOUBLE = 3,
 171             ITEM_LONG = 4,
 172             ITEM_NULL = 5,
 173             ITEM_UNINITIALIZED_THIS = 6,
 174             ITEM_OBJECT = 7,
 175             ITEM_UNINITIALIZED = 8,
 176             ITEM_BOOLEAN = 9,
 177             ITEM_BYTE = 10,
 178             ITEM_SHORT = 11,
 179             ITEM_CHAR = 12,
 180             ITEM_LONG_2ND = 13,
 181             ITEM_DOUBLE_2ND = 14,
 182             ITEM_BOGUS = -1;
 183 
 184     // Ranges represented by these constants are inclusive on both ends
 185     public static final int
 186             SAME_FRAME_END = 63,
 187             SAME_LOCALS_1_STACK_ITEM_FRAME_START = 64,
 188             SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127,
 189             RESERVED_END = 245,
 190             EARLY_LARVAL = 246,
 191             SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
 192             CHOP_FRAME_START = 248,
 193             CHOP_FRAME_END = 250,
 194             SAME_FRAME_EXTENDED = 251,
 195             APPEND_FRAME_START = 252,
 196             APPEND_FRAME_END = 254,
 197             FULL_FRAME = 255;
 198 
 199     private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
 200         Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
 201         Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
 202 
 203     static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
 204 
 205     private final Type thisType;
 206     private final String methodName;
 207     private final MethodTypeDesc methodDesc;
 208     private final RawBytecodeHelper.CodeRange bytecode;
 209     private final SplitConstantPool cp;
 210     private final boolean isStatic;
 211     private final LabelContext labelContext;
 212     private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
 213     private final List<RawExceptionCatch> rawHandlers;
 214     private final ClassHierarchyImpl classHierarchy;
 215     private final UnsetField[] strictFieldsToPut; // exact-sized, do not modify this copy!
 216     private final boolean patchDeadCode;
 217     private final boolean filterDeadLabels;
 218     private Frame[] frames = EMPTY_FRAME_ARRAY;
 219     private int framesCount = 0;
 220     private final Frame currentFrame;
 221     private int maxStack, maxLocals;
 222 
 223     /**
 224      * Primary constructor of the <code>Generator</code> class.
 225      * New <code>Generator</code> instance must be created for each individual class/method.
 226      * Instance contains only immutable results, all the calculations are processed during instance construction.
 227      *
 228      * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
 229      * labels to bytecode offsets (or vice versa)
 230      * @param thisClass class to generate stack maps for
 231      * @param methodName method name to generate stack maps for
 232      * @param methodDesc method descriptor to generate stack maps for
 233      * @param isStatic information whether the method is static
 234      * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects  and patches dead code
 235      * @param cp R/W <code>ConstantPoolBuilder</code> instance used to resolve all involved CP entries and also generate new entries referenced from the generated stack maps
 236      * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
 237      * and also to be altered when dead code is detected and must be excluded from exception handlers
 238      */
 239     public StackMapGenerator(LabelContext labelContext,
 240                      ClassDesc thisClass,
 241                      String methodName,
 242                      MethodTypeDesc methodDesc,
 243                      boolean isStatic,
 244                      RawBytecodeHelper.CodeRange bytecode,
 245                      SplitConstantPool cp,
 246                      ClassFileImpl context,
 247                      UnsetField[] strictFields,
 248                      List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
 249         this.thisType = Type.referenceType(thisClass);
 250         this.methodName = methodName;
 251         this.methodDesc = methodDesc;
 252         this.isStatic = isStatic;
 253         this.bytecode = bytecode;
 254         this.cp = cp;
 255         this.labelContext = labelContext;
 256         this.handlers = handlers;
 257         this.rawHandlers = new ArrayList<>(handlers.size());
 258         this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
 259         this.patchDeadCode = context.patchDeadCode();
 260         this.filterDeadLabels = context.dropDeadLabels();
 261         this.currentFrame = new Frame(classHierarchy);
 262         if (OBJECT_INITIALIZER_NAME.equals(methodName)) {
 263             this.strictFieldsToPut = strictFields;
 264         } else {
 265             this.strictFieldsToPut = UnsetField.EMPTY_ARRAY;
 266         }
 267         generate();
 268     }
 269 
 270     /**
 271      * Calculated maximum number of the locals required
 272      * @return maximum number of the locals required
 273      */
 274     public int maxLocals() {
 275         return maxLocals;
 276     }
 277 
 278     /**
 279      * Calculated maximum stack size required
 280      * @return maximum stack size required
 281      */
 282     public int maxStack() {
 283         return maxStack;
 284     }
 285 
 286     private Frame getFrame(int offset) {

 395             } else {
 396                 //split
 397                 Label newStart = labelContext.newLabel();
 398                 labelContext.setLabelTarget(newStart, rangeEnd);
 399                 Label newEnd = labelContext.newLabel();
 400                 labelContext.setLabelTarget(newEnd, rangeStart);
 401                 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
 402                 it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
 403             }
 404         }
 405     }
 406 
 407     /**
 408      * Getter of the generated <code>StackMapTableAttribute</code> or null if stack map is empty
 409      * @return <code>StackMapTableAttribute</code> or null if stack map is empty
 410      */
 411     public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
 412         return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
 413             @Override
 414             public void writeBody(BufWriterImpl b) {
 415                 if (framesCount != (char) framesCount) {
 416                     throw generatorError("Too many frames: " + framesCount);
 417                 }
 418                 b.writeU2(framesCount);
 419                 Frame prevFrame =  new Frame(classHierarchy);
 420                 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
 421                 prevFrame.trimAndCompress();
 422                 for (int i = 0; i < framesCount; i++) {
 423                     var fr = frames[i];
 424                     fr.trimAndCompress();
 425                     fr.writeTo(b, prevFrame, cp);
 426                     prevFrame = fr;
 427                 }
 428             }
 429 
 430             @Override
 431             public Utf8Entry attributeName() {
 432                 return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
 433             }
 434         };
 435     }
 436 
 437     private static Type cpIndexToType(int index, ConstantPoolBuilder cp) {
 438         return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol());
 439     }
 440 
 441     private void processMethod() {
 442         var frames = this.frames;
 443         var currentFrame = this.currentFrame;
 444         currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
 445         currentFrame.stackSize = 0;

 446         currentFrame.offset = -1;
 447         int stackmapIndex = 0;
 448         var bcs = bytecode.start();
 449         boolean ncf = false;
 450         while (bcs.next()) {
 451             currentFrame.offset = bcs.bci();
 452             if (stackmapIndex < framesCount) {
 453                 int thisOffset = frames[stackmapIndex].offset;
 454                 if (ncf && thisOffset > bcs.bci()) {
 455                     throw generatorError("Expecting a stack map frame");
 456                 }
 457                 if (thisOffset == bcs.bci()) {
 458                     Frame nextFrame = frames[stackmapIndex++];
 459                     if (!ncf) {
 460                         currentFrame.checkAssignableTo(nextFrame);
 461                     }
 462                     while (!nextFrame.dirty) { //skip unmatched frames
 463                         if (stackmapIndex == framesCount) return; //skip the rest of this round
 464                         nextFrame = frames[stackmapIndex++];
 465                     }

 468                     currentFrame.offset = bcs.bci();
 469                     currentFrame.copyFrom(nextFrame);
 470                     nextFrame.dirty = false;
 471                 } else if (thisOffset < bcs.bci()) {
 472                     throw generatorError("Bad stack map offset");
 473                 }
 474             } else if (ncf) {
 475                 throw generatorError("Expecting a stack map frame");
 476             }
 477             ncf = processBlock(bcs);
 478         }
 479     }
 480 
 481     private boolean processBlock(RawBytecodeHelper bcs) {
 482         int opcode = bcs.opcode();
 483         boolean ncf = false;
 484         boolean this_uninit = false;
 485         boolean verified_exc_handlers = false;
 486         int bci = bcs.bci();
 487         Type type1, type2, type3, type4;
 488         if ((RawBytecodeHelper.isStoreIntoLocal(opcode) || (opcode == PUTFIELD && OBJECT_INITIALIZER_NAME.equals(methodName)))
 489                 && bci >= exMin && bci < exMax) {
 490             processExceptionHandlerTargets(bci, this_uninit);
 491             verified_exc_handlers = true;
 492         }
 493         switch (opcode) {
 494             case NOP -> {}
 495             case RETURN -> {
 496                 ncf = true;
 497             }
 498             case ACONST_NULL ->
 499                 currentFrame.pushStack(Type.NULL_TYPE);
 500             case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH ->
 501                 currentFrame.pushStack(Type.INTEGER_TYPE);
 502             case LCONST_0, LCONST_1 ->
 503                 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 504             case FCONST_0, FCONST_1, FCONST_2 ->
 505                 currentFrame.pushStack(Type.FLOAT_TYPE);
 506             case DCONST_0, DCONST_1 ->
 507                 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 508             case LDC ->
 509                 processLdc(bcs.getIndexU1());

 692                 type1 = cpIndexToType(bcs.getIndexU2(), cp);
 693                 int dim = bcs.getU1Unchecked(bcs.bci() + 3);
 694                 for (int i = 0; i < dim; i++) {
 695                     currentFrame.popStack();
 696                 }
 697                 currentFrame.pushStack(type1);
 698             }
 699             case JSR, JSR_W, RET ->
 700                 throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0");
 701             default ->
 702                 throw generatorError(String.format("Bad instruction: %02x", opcode));
 703         }
 704         if (!verified_exc_handlers && bci >= exMin && bci < exMax) {
 705             processExceptionHandlerTargets(bci, this_uninit);
 706         }
 707         return ncf;
 708     }
 709 
 710     private void processExceptionHandlerTargets(int bci, boolean this_uninit) {
 711         for (var ex : rawHandlers) {
 712             if (bci == ex.start || (currentFrame.localsOrUnsetsChanged && bci > ex.start && bci < ex.end)) {
 713                 int flags = currentFrame.flags;
 714                 if (this_uninit) flags |= FLAG_THIS_UNINIT;
 715                 Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType);
 716                 checkJumpTarget(newFrame, ex.handler);
 717             }
 718         }
 719         currentFrame.localsOrUnsetsChanged = false;
 720     }
 721 
 722     private void processLdc(int index) {
 723         switch (cp.entryByIndex(index).tag()) {
 724             case TAG_UTF8 ->
 725                 currentFrame.pushStack(Type.OBJECT_TYPE);
 726             case TAG_STRING ->
 727                 currentFrame.pushStack(Type.STRING_TYPE);
 728             case TAG_CLASS ->
 729                 currentFrame.pushStack(Type.CLASS_TYPE);
 730             case TAG_INTEGER ->
 731                 currentFrame.pushStack(Type.INTEGER_TYPE);
 732             case TAG_FLOAT ->
 733                 currentFrame.pushStack(Type.FLOAT_TYPE);
 734             case TAG_DOUBLE ->
 735                 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 736             case TAG_LONG ->
 737                 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 738             case TAG_METHOD_HANDLE ->
 739                 currentFrame.pushStack(Type.METHOD_HANDLE_TYPE);

 769                 throw generatorError("number of keys in lookupswitch less than 0");
 770             }
 771             delta = 2;
 772             for (int i = 0; i < (keys - 1); i++) {
 773                 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
 774                 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
 775                 if (this_key >= next_key) {
 776                     throw generatorError("Bad lookupswitch instruction");
 777                 }
 778             }
 779         }
 780         int target = bci + defaultOffset;
 781         checkJumpTarget(currentFrame, target);
 782         for (int i = 0; i < keys; i++) {
 783             target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
 784             checkJumpTarget(currentFrame, target);
 785         }
 786     }
 787 
 788     private void processFieldInstructions(RawBytecodeHelper bcs) {
 789         var nameAndType = cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType();
 790         var desc = Util.fieldTypeSymbol(nameAndType.type());
 791         var currentFrame = this.currentFrame;
 792         switch (bcs.opcode()) {
 793             case GETSTATIC ->
 794                 currentFrame.pushStack(desc);
 795             case PUTSTATIC -> {
 796                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
 797             }
 798             case GETFIELD -> {
 799                 currentFrame.decStack(1);
 800                 currentFrame.pushStack(desc);
 801             }
 802             case PUTFIELD -> {
 803                 if (strictFieldsToPut.length > 0) {
 804                     currentFrame.putStrictField(nameAndType);
 805                 }
 806                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
 807             }
 808             default -> throw new AssertionError("Should not reach here");
 809         }
 810     }
 811 
 812     private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) {
 813         int index = bcs.getIndexU2();
 814         int opcode = bcs.opcode();
 815         var nameAndType = opcode == INVOKEDYNAMIC
 816                 ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType()
 817                 : cp.entryByIndex(index, MemberRefEntry.class).nameAndType();
 818         var mDesc = Util.methodTypeSymbol(nameAndType.type());
 819         int bci = bcs.bci();
 820         var currentFrame = this.currentFrame;
 821         currentFrame.decStack(Util.parameterSlots(mDesc));
 822         if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) {
 823             if (nameAndType.name().equalsString(OBJECT_INITIALIZER_NAME)) {
 824                 Type type = currentFrame.popStack();
 825                 if (type == Type.UNITIALIZED_THIS_TYPE) {
 826                     if (inTryBlock) {
 827                         processExceptionHandlerTargets(bci, true);
 828                     }
 829                     var owner = cp.entryByIndex(index, MemberRefEntry.class).owner();
 830                     if (!owner.name().equalsString(((ClassOrInterfaceDescImpl) thisType.sym).internalName())
 831                             && currentFrame.unsetFieldsSize != 0) {
 832                         throw generatorError("Unset fields mismatch");
 833                     }
 834                     currentFrame.initializeObject(type, thisType);
 835                     currentFrame.unsetFieldsSize = 0;
 836                     currentFrame.unsetFields = UnsetField.EMPTY_ARRAY;
 837                     thisUninit = true;
 838                 } else if (type.tag == ITEM_UNINITIALIZED) {
 839                     Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
 840                     if (inTryBlock) {
 841                         processExceptionHandlerTargets(bci, thisUninit);
 842                     }
 843                     currentFrame.initializeObject(type, new_class_type);
 844                 } else {
 845                     throw generatorError("Bad operand type when invoking <init>");
 846                 }
 847             } else {
 848                 currentFrame.decStack(1);
 849             }
 850         }
 851         currentFrame.pushStack(mDesc.returnType());
 852         return thisUninit;
 853     }
 854 
 855     private Type getNewarrayType(int index) {
 856         if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));

 959                 return;
 960             }
 961             if (frameOffset > offset) {
 962                 break;
 963             }
 964         }
 965         if (framesCount >= frames.length) {
 966             int newCapacity = framesCount + 8;
 967             this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
 968         }
 969         if (i != framesCount) {
 970             System.arraycopy(frames, i, frames, i + 1, framesCount - i);
 971         }
 972         frames[i] = new Frame(offset, classHierarchy);
 973         this.framesCount = framesCount + 1;
 974     }
 975 
 976     private final class Frame {
 977 
 978         int offset;
 979         int localsSize, stackSize, unsetFieldsSize;
 980         int flags;
 981         int frameMaxStack = 0, frameMaxLocals = 0;
 982         boolean dirty = false;
 983         boolean localsOrUnsetsChanged = false;
 984 
 985         private final ClassHierarchyImpl classHierarchy;
 986 
 987         private Type[] locals, stack;
 988         private UnsetField[] unsetFields; // sorted, modifiable oversized array
 989 
 990         Frame(ClassHierarchyImpl classHierarchy) {
 991             this(-1, 0, 0, 0, 0, null, null, UnsetField.EMPTY_ARRAY, classHierarchy);
 992         }
 993 
 994         Frame(int offset, ClassHierarchyImpl classHierarchy) {
 995             this(offset, -1, 0, 0, 0, null, null, UnsetField.EMPTY_ARRAY, classHierarchy);
 996         }
 997 
 998         Frame(int offset, int flags, int locals_size, int stack_size, int unsetFieldsSize, Type[] locals, Type[] stack, UnsetField[] unsetFields, ClassHierarchyImpl classHierarchy) {
 999             this.offset = offset;
1000             this.localsSize = locals_size;
1001             this.stackSize = stack_size;
1002             this.unsetFieldsSize = unsetFieldsSize;
1003             this.flags = flags;
1004             this.locals = locals;
1005             this.stack = stack;
1006             this.unsetFields = unsetFields;
1007             this.classHierarchy = classHierarchy;
1008         }
1009 
1010         @Override
1011         public String toString() {
1012             return (dirty ? "frame* @" : "frame @") + offset +
1013                     " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) +
1014                     " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)) +
1015                     " and unset fields " + (unsetFields == null ? "[]" : Arrays.asList(unsetFields).subList(0, unsetFieldsSize));
1016         }
1017 
1018         Frame pushStack(ClassDesc desc) {
1019             if (desc == CD_long)   return pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
1020             if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
1021             return desc == CD_void ? this
1022                     : pushStack(
1023                     desc.isPrimitive()
1024                             ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
1025                             : Type.referenceType(desc));
1026         }
1027 
1028         Frame pushStack(Type type) {
1029             checkStack(stackSize);
1030             stack[stackSize++] = type;
1031             return this;
1032         }
1033 
1034         Frame pushStack(Type type1, Type type2) {
1035             checkStack(stackSize + 1);
1036             stack[stackSize++] = type1;
1037             stack[stackSize++] = type2;
1038             return this;
1039         }
1040 
1041         Type popStack() {
1042             if (stackSize < 1) throw generatorError("Operand stack underflow");
1043             return stack[--stackSize];
1044         }
1045 
1046         Frame decStack(int size) {
1047             stackSize -= size;
1048             if (stackSize < 0) throw generatorError("Operand stack underflow");
1049             return this;
1050         }
1051 
1052         Frame frameInExceptionHandler(int flags, Type excType) {
1053             return new Frame(offset, flags, localsSize, 1, unsetFieldsSize,
1054                     locals, new Type[] {excType}, unsetFields, classHierarchy);
1055         }
1056 
1057         void initializeObject(Type old_object, Type new_object) {
1058             int i;
1059             for (i = 0; i < localsSize; i++) {
1060                 if (locals[i].equals(old_object)) {
1061                     locals[i] = new_object;
1062                     localsOrUnsetsChanged = true;
1063                 }
1064             }
1065             for (i = 0; i < stackSize; i++) {
1066                 if (stack[i].equals(old_object)) {
1067                     stack[i] = new_object;
1068                 }
1069             }
1070             if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1071                 flags &= ~FLAG_THIS_UNINIT;
1072                 assert flags == 0 : flags;
1073             }
1074         }
1075 
1076         Frame checkLocal(int index) {
1077             if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1078             if (locals == null) {
1079                 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1080                 Arrays.fill(locals, Type.TOP_TYPE);
1081             } else if (index >= locals.length) {
1082                 int current = locals.length;
1083                 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1084                 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1085             }
1086             return this;
1087         }
1088 
1089         void putStrictField(NameAndTypeEntry nat) {
1090             int shift = 0;
1091             var array = unsetFields;
1092             for (int i = 0; i < unsetFieldsSize; i++) {
1093                 var f = array[i];
1094                 if (f.name().equals(nat.name()) && f.type().equals(nat.type())) {
1095                     shift++;
1096                 } else if (shift != 0) {
1097                     array[i - shift] = array[i];
1098                     array[i] = null;
1099                 }
1100             }
1101             if (shift > 1) {
1102                 throw generatorError(nat + "; discovered " + shift);
1103             } else if (shift == 1) {
1104                 localsOrUnsetsChanged = true;
1105             }
1106             unsetFieldsSize -= shift;
1107         }
1108 
1109         private void checkStack(int index) {
1110             if (index >= frameMaxStack) frameMaxStack = index + 1;
1111             if (stack == null) {
1112                 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1113                 Arrays.fill(stack, Type.TOP_TYPE);
1114             } else if (index >= stack.length) {
1115                 int current = stack.length;
1116                 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1117                 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1118             }
1119         }
1120 
1121         private void setLocalRawInternal(int index, Type type) {
1122             checkLocal(index);
1123             localsOrUnsetsChanged |= !type.equals(locals[index]);
1124             locals[index] = type;
1125         }
1126 
1127         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass, UnsetField[] strictFieldsToPut) {
1128             int localsSize = 0;
1129             // Pre-emptively create a locals array that encompass all parameter slots
1130             checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1131             Type type;
1132             Type[] locals = this.locals;
1133             if (!isStatic) {
1134                 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
1135                     int strictFieldCount = strictFieldsToPut.length;
1136                     this.unsetFields = UnsetField.copyArray(strictFieldsToPut, strictFieldCount);
1137                     this.unsetFieldsSize = strictFieldCount;
1138                     type = Type.UNITIALIZED_THIS_TYPE;
1139                     this.flags = FLAG_THIS_UNINIT;
1140                 } else {
1141                     this.unsetFields = UnsetField.EMPTY_ARRAY;
1142                     this.unsetFieldsSize = 0;
1143                     type = thisKlass;
1144                     this.flags = 0;
1145                 }
1146                 locals[localsSize++] = type;
1147             }
1148             for (int i = 0; i < methodDesc.parameterCount(); i++) {
1149                 var desc = methodDesc.parameterType(i);
1150                 if (desc == CD_long) {
1151                     locals[localsSize    ] = Type.LONG_TYPE;
1152                     locals[localsSize + 1] = Type.LONG2_TYPE;
1153                     localsSize += 2;
1154                 } else if (desc == CD_double) {
1155                     locals[localsSize    ] = Type.DOUBLE_TYPE;
1156                     locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1157                     localsSize += 2;
1158                 } else {
1159                     if (!desc.isPrimitive()) {
1160                         type = Type.referenceType(desc);
1161                     } else if (desc == CD_float) {
1162                         type = Type.FLOAT_TYPE;
1163                     } else {
1164                         type = Type.INTEGER_TYPE;
1165                     }
1166                     locals[localsSize++] = type;
1167                 }
1168             }
1169             this.localsSize = localsSize;
1170         }
1171 
1172         void copyFrom(Frame src) {
1173             if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1174             localsSize = src.localsSize;
1175             checkLocal(src.localsSize - 1);
1176             if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1177             if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1178             stackSize = src.stackSize;
1179             checkStack(src.stackSize - 1);
1180             if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);
1181             unsetFieldsSize = src.unsetFieldsSize;
1182             unsetFields = UnsetField.copyArray(src.unsetFields, src.unsetFieldsSize);
1183             flags = src.flags;
1184             localsOrUnsetsChanged = true;
1185         }
1186 
1187         void checkAssignableTo(Frame target) {
1188             int localsSize = this.localsSize;
1189             int stackSize = this.stackSize;
1190             int myUnsetFieldsSize = this.unsetFieldsSize;
1191             if (target.flags == -1) {
1192                 target.locals = locals == null ? null : locals.clone();
1193                 target.localsSize = localsSize;
1194                 if (stackSize > 0) {
1195                     target.stack = stack.clone();
1196                     target.stackSize = stackSize;
1197                 }
1198                 target.unsetFields = UnsetField.copyArray(this.unsetFields, myUnsetFieldsSize);
1199                 target.unsetFieldsSize = myUnsetFieldsSize;
1200                 target.flags = flags;
1201                 target.dirty = true;
1202             } else {
1203                 if (target.localsSize > localsSize) {
1204                     target.localsSize = localsSize;
1205                     target.dirty = true;
1206                 }
1207                 for (int i = 0; i < target.localsSize; i++) {
1208                     merge(locals[i], target.locals, i, target);
1209                 }
1210                 if (stackSize != target.stackSize) {
1211                     throw generatorError("Stack size mismatch");
1212                 }
1213                 for (int i = 0; i < target.stackSize; i++) {
1214                     if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1215                         throw generatorError("Stack content mismatch");
1216                     }
1217                 }
1218                 if (myUnsetFieldsSize != 0) {
1219                     mergeUnsetFields(target);
1220                 }
1221             }
1222         }
1223 
1224         private Type getLocalRawInternal(int index) {
1225             checkLocal(index);
1226             return locals[index];
1227         }
1228 
1229         Type getLocal(int index) {
1230             Type ret = getLocalRawInternal(index);
1231             if (index >= localsSize) {
1232                 localsSize = index + 1;
1233             }
1234             return ret;
1235         }
1236 
1237         void setLocal(int index, Type type) {
1238             Type old = getLocalRawInternal(index);
1239             if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1240                 setLocalRawInternal(index + 1, Type.TOP_TYPE);

1257             if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1258                 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1259             }
1260             setLocalRawInternal(index, type1);
1261             setLocalRawInternal(index + 1, type2);
1262             if (index >= localsSize - 1) {
1263                 localsSize = index + 2;
1264             }
1265         }
1266 
1267         private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1268             var to = toTypes[i];
1269             var newTo = to.mergeFrom(me, classHierarchy);
1270             if (to != newTo && !to.equals(newTo)) {
1271                 toTypes[i] = newTo;
1272                 target.dirty = true;
1273             }
1274             return newTo;
1275         }
1276 
1277         // Merge this frame's unset fields into the target frame
1278         private void mergeUnsetFields(Frame target) {
1279             int myUnsetSize = unsetFieldsSize;
1280             int targetUnsetSize = target.unsetFieldsSize;
1281             var myUnsets = unsetFields;
1282             var targetUnsets = target.unsetFields;
1283             if (UnsetField.matches(myUnsets, myUnsetSize, targetUnsets, targetUnsetSize)) {
1284                 return; // no merge
1285             }
1286             // merge sort
1287             var merged = new UnsetField[StackMapGenerator.this.strictFieldsToPut.length];
1288             int mergedSize = 0;
1289             int i = 0;
1290             int j = 0;
1291             while (i < myUnsetSize && j < targetUnsetSize) {
1292                 var myCandidate = myUnsets[i];
1293                 var targetCandidate = targetUnsets[j];
1294                 var cmp = myCandidate.compareTo(targetCandidate);
1295                 if (cmp == 0) {
1296                     merged[mergedSize++] = myCandidate;
1297                     i++;
1298                     j++;
1299                 } else if (cmp < 0) {
1300                     merged[mergedSize++] = myCandidate;
1301                     i++;
1302                 } else {
1303                     merged[mergedSize++] = targetCandidate;
1304                     j++;
1305                 }
1306             }
1307             if (i < myUnsetSize) {
1308                 int len = myUnsetSize - i;
1309                 System.arraycopy(myUnsets, i, merged, mergedSize, len);
1310                 mergedSize += len;
1311             } else if (j < targetUnsetSize) {
1312                 int len = targetUnsetSize - j;
1313                 System.arraycopy(targetUnsets, j, merged, mergedSize, len);
1314                 mergedSize += len;
1315             }
1316 
1317             target.unsetFieldsSize = mergedSize;
1318             target.unsetFields = merged;
1319             target.dirty = true;
1320         }
1321 
1322         private static int trimAndCompress(Type[] types, int count) {
1323             while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1324             int compressed = 0;
1325             for (int i = 0; i < count; i++) {
1326                 if (!types[i].isCategory2_2nd()) {
1327                     if (compressed != i) {
1328                         types[compressed] = types[i];
1329                     }
1330                     compressed++;
1331                 }
1332             }
1333             return compressed;
1334         }
1335 
1336         void trimAndCompress() {
1337             localsSize = trimAndCompress(locals, localsSize);
1338             stackSize = trimAndCompress(stack, stackSize);
1339         }
1340 
1341         boolean hasUninitializedThis() {
1342             int size = this.localsSize;
1343             var localVars = this.locals;
1344             for (int i = 0; i < size; i++) {
1345                 if (localVars[i] == Type.UNITIALIZED_THIS_TYPE)
1346                     return true;
1347             }
1348             return false;
1349         }
1350 
1351         private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1352             if (l1 == null || l2 == null) return commonSize == 0;
1353             return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1354         }
1355 
1356         // In sync with StackMapDecoder::needsLarvalFrameForTransition
1357         private boolean needsLarvalFrame(Frame prevFrame) {
1358             if (UnsetField.matches(unsetFields, unsetFieldsSize, prevFrame.unsetFields, prevFrame.unsetFieldsSize))
1359                 return false;
1360             if (!hasUninitializedThis()) {
1361                 assert unsetFieldsSize == 0 : this; // Should have been handled by processInvokeInstructions
1362                 return false;
1363             }
1364             return true;
1365         }
1366 
1367         void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
1368             // enclosing frames
1369             if (needsLarvalFrame(prevFrame)) {
1370                 out.writeU1U2(EARLY_LARVAL, unsetFieldsSize);
1371                 for (int i = 0; i < unsetFieldsSize; i++) {
1372                     var f = unsetFields[i];
1373                     out.writeIndex(cp.nameAndTypeEntry(f.name(), f.type()));
1374                 }
1375             }
1376             // base frame
1377             int localsSize = this.localsSize;
1378             int stackSize = this.stackSize;
1379             int offsetDelta = offset - prevFrame.offset - 1;
1380             if (stackSize == 0) {
1381                 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1382                 int diffLocalsSize = localsSize - prevFrame.localsSize;
1383                 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1384                     if (diffLocalsSize == 0 && offsetDelta <= SAME_FRAME_END) { //same frame
1385                         out.writeU1(offsetDelta);
1386                     } else {   //chop, same extended or append frame
1387                         out.writeU1U2(SAME_FRAME_EXTENDED + diffLocalsSize, offsetDelta);
1388                         for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1389                     }
1390                     return;
1391                 }
1392             } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1393                 if (offsetDelta <= SAME_LOCALS_1_STACK_ITEM_FRAME_END  - SAME_LOCALS_1_STACK_ITEM_FRAME_START) {  //same locals 1 stack item frame
1394                     out.writeU1(SAME_LOCALS_1_STACK_ITEM_FRAME_START + offsetDelta);
1395                 } else {  //same locals 1 stack item extended frame
1396                     out.writeU1U2(SAME_LOCALS_1_STACK_ITEM_EXTENDED, offsetDelta);
< prev index next >