< prev index next >

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

Print this page

   1 /*
   2  * Copyright (c) 2022, 2024, 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     private static final int ITEM_TOP = 0,
 170             ITEM_INTEGER = 1,
 171             ITEM_FLOAT = 2,
 172             ITEM_DOUBLE = 3,
 173             ITEM_LONG = 4,
 174             ITEM_NULL = 5,
 175             ITEM_UNINITIALIZED_THIS = 6,
 176             ITEM_OBJECT = 7,
 177             ITEM_UNINITIALIZED = 8,
 178             ITEM_BOOLEAN = 9,
 179             ITEM_BYTE = 10,

 181             ITEM_CHAR = 12,
 182             ITEM_LONG_2ND = 13,
 183             ITEM_DOUBLE_2ND = 14;
 184 
 185     private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
 186         Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
 187         Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
 188 
 189     static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
 190 
 191     private final Type thisType;
 192     private final String methodName;
 193     private final MethodTypeDesc methodDesc;
 194     private final RawBytecodeHelper.CodeRange bytecode;
 195     private final SplitConstantPool cp;
 196     private final boolean isStatic;
 197     private final LabelContext labelContext;
 198     private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
 199     private final List<RawExceptionCatch> rawHandlers;
 200     private final ClassHierarchyImpl classHierarchy;

 201     private final boolean patchDeadCode;
 202     private final boolean filterDeadLabels;
 203     private Frame[] frames = EMPTY_FRAME_ARRAY;
 204     private int framesCount = 0;
 205     private final Frame currentFrame;
 206     private int maxStack, maxLocals;
 207 
 208     /**
 209      * Primary constructor of the <code>Generator</code> class.
 210      * New <code>Generator</code> instance must be created for each individual class/method.
 211      * Instance contains only immutable results, all the calculations are processed during instance construction.
 212      *
 213      * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
 214      * labels to bytecode offsets (or vice versa)
 215      * @param thisClass class to generate stack maps for
 216      * @param methodName method name to generate stack maps for
 217      * @param methodDesc method descriptor to generate stack maps for
 218      * @param isStatic information whether the method is static
 219      * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects  and patches dead code
 220      * @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
 221      * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
 222      * and also to be altered when dead code is detected and must be excluded from exception handlers
 223      */
 224     public StackMapGenerator(LabelContext labelContext,
 225                      ClassDesc thisClass,
 226                      String methodName,
 227                      MethodTypeDesc methodDesc,
 228                      boolean isStatic,
 229                      RawBytecodeHelper.CodeRange bytecode,
 230                      SplitConstantPool cp,
 231                      ClassFileImpl context,

 232                      List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
 233         this.thisType = Type.referenceType(thisClass);
 234         this.methodName = methodName;
 235         this.methodDesc = methodDesc;
 236         this.isStatic = isStatic;
 237         this.bytecode = bytecode;
 238         this.cp = cp;
 239         this.labelContext = labelContext;
 240         this.handlers = handlers;
 241         this.rawHandlers = new ArrayList<>(handlers.size());
 242         this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
 243         this.patchDeadCode = context.patchDeadCode();
 244         this.filterDeadLabels = context.dropDeadLabels();
 245         this.currentFrame = new Frame(classHierarchy);





 246         generate();
 247     }
 248 
 249     /**
 250      * Calculated maximum number of the locals required
 251      * @return maximum number of the locals required
 252      */
 253     public int maxLocals() {
 254         return maxLocals;
 255     }
 256 
 257     /**
 258      * Calculated maximum stack size required
 259      * @return maximum stack size required
 260      */
 261     public int maxStack() {
 262         return maxStack;
 263     }
 264 
 265     private Frame getFrame(int offset) {

 374             } else {
 375                 //split
 376                 Label newStart = labelContext.newLabel();
 377                 labelContext.setLabelTarget(newStart, rangeEnd);
 378                 Label newEnd = labelContext.newLabel();
 379                 labelContext.setLabelTarget(newEnd, rangeStart);
 380                 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
 381                 it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
 382             }
 383         }
 384     }
 385 
 386     /**
 387      * Getter of the generated <code>StackMapTableAttribute</code> or null if stack map is empty
 388      * @return <code>StackMapTableAttribute</code> or null if stack map is empty
 389      */
 390     public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
 391         return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
 392             @Override
 393             public void writeBody(BufWriterImpl b) {

 394                 b.writeU2(framesCount);

 395                 Frame prevFrame =  new Frame(classHierarchy);
 396                 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
 397                 prevFrame.trimAndCompress();
 398                 for (int i = 0; i < framesCount; i++) {
 399                     var fr = frames[i];
 400                     fr.trimAndCompress();
 401                     fr.writeTo(b, prevFrame, cp);
 402                     prevFrame = fr;
 403                 }







 404             }
 405 
 406             @Override
 407             public Utf8Entry attributeName() {
 408                 return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
 409             }
 410         };
 411     }
 412 
 413     private static Type cpIndexToType(int index, ConstantPoolBuilder cp) {
 414         return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol());
 415     }
 416 
 417     private void processMethod() {
 418         var frames = this.frames;
 419         var currentFrame = this.currentFrame;
 420         currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
 421         currentFrame.stackSize = 0;
 422         currentFrame.flags = 0;
 423         currentFrame.offset = -1;
 424         int stackmapIndex = 0;
 425         var bcs = bytecode.start();
 426         boolean ncf = false;
 427         while (bcs.next()) {
 428             currentFrame.offset = bcs.bci();
 429             if (stackmapIndex < framesCount) {
 430                 int thisOffset = frames[stackmapIndex].offset;
 431                 if (ncf && thisOffset > bcs.bci()) {
 432                     throw generatorError("Expecting a stack map frame");
 433                 }
 434                 if (thisOffset == bcs.bci()) {
 435                     Frame nextFrame = frames[stackmapIndex++];
 436                     if (!ncf) {
 437                         currentFrame.checkAssignableTo(nextFrame);
 438                     }
 439                     while (!nextFrame.dirty) { //skip unmatched frames
 440                         if (stackmapIndex == framesCount) return; //skip the rest of this round
 441                         nextFrame = frames[stackmapIndex++];
 442                     }

 745                 throw generatorError("number of keys in lookupswitch less than 0");
 746             }
 747             delta = 2;
 748             for (int i = 0; i < (keys - 1); i++) {
 749                 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
 750                 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
 751                 if (this_key >= next_key) {
 752                     throw generatorError("Bad lookupswitch instruction");
 753                 }
 754             }
 755         }
 756         int target = bci + defaultOffset;
 757         checkJumpTarget(currentFrame, target);
 758         for (int i = 0; i < keys; i++) {
 759             target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
 760             checkJumpTarget(currentFrame, target);
 761         }
 762     }
 763 
 764     private void processFieldInstructions(RawBytecodeHelper bcs) {
 765         var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).type());

 766         var currentFrame = this.currentFrame;
 767         switch (bcs.opcode()) {
 768             case GETSTATIC ->
 769                 currentFrame.pushStack(desc);
 770             case PUTSTATIC -> {
 771                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
 772             }
 773             case GETFIELD -> {
 774                 currentFrame.decStack(1);
 775                 currentFrame.pushStack(desc);
 776             }
 777             case PUTFIELD -> {



 778                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
 779             }
 780             default -> throw new AssertionError("Should not reach here");
 781         }
 782     }
 783 
 784     private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) {
 785         int index = bcs.getIndexU2();
 786         int opcode = bcs.opcode();
 787         var nameAndType = opcode == INVOKEDYNAMIC
 788                 ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType()
 789                 : cp.entryByIndex(index, MemberRefEntry.class).nameAndType();
 790         var mDesc = Util.methodTypeSymbol(nameAndType.type());
 791         int bci = bcs.bci();
 792         var currentFrame = this.currentFrame;
 793         currentFrame.decStack(Util.parameterSlots(mDesc));
 794         if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) {
 795             if (nameAndType.name().equalsString(OBJECT_INITIALIZER_NAME)) {
 796                 Type type = currentFrame.popStack();
 797                 if (type == Type.UNITIALIZED_THIS_TYPE) {
 798                     if (inTryBlock) {
 799                         processExceptionHandlerTargets(bci, true);
 800                     }





 801                     currentFrame.initializeObject(type, thisType);


 802                     thisUninit = true;
 803                 } else if (type.tag == ITEM_UNINITIALIZED) {
 804                     Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
 805                     if (inTryBlock) {
 806                         processExceptionHandlerTargets(bci, thisUninit);
 807                     }
 808                     currentFrame.initializeObject(type, new_class_type);
 809                 } else {
 810                     throw generatorError("Bad operand type when invoking <init>");
 811                 }
 812             } else {
 813                 currentFrame.decStack(1);
 814             }
 815         }
 816         currentFrame.pushStack(mDesc.returnType());
 817         return thisUninit;
 818     }
 819 
 820     private Type getNewarrayType(int index) {
 821         if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));

 924                 return;
 925             }
 926             if (frameOffset > offset) {
 927                 break;
 928             }
 929         }
 930         if (framesCount >= frames.length) {
 931             int newCapacity = framesCount + 8;
 932             this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
 933         }
 934         if (i != framesCount) {
 935             System.arraycopy(frames, i, frames, i + 1, framesCount - i);
 936         }
 937         frames[i] = new Frame(offset, classHierarchy);
 938         this.framesCount = framesCount + 1;
 939     }
 940 
 941     private final class Frame {
 942 
 943         int offset;
 944         int localsSize, stackSize;
 945         int flags;
 946         int frameMaxStack = 0, frameMaxLocals = 0;
 947         boolean dirty = false;
 948         boolean localsChanged = false;
 949 
 950         private final ClassHierarchyImpl classHierarchy;
 951 
 952         private Type[] locals, stack;

 953 
 954         Frame(ClassHierarchyImpl classHierarchy) {
 955             this(-1, 0, 0, 0, null, null, classHierarchy);
 956         }
 957 
 958         Frame(int offset, ClassHierarchyImpl classHierarchy) {
 959             this(offset, -1, 0, 0, null, null, classHierarchy);
 960         }
 961 
 962         Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) {
 963             this.offset = offset;
 964             this.localsSize = locals_size;
 965             this.stackSize = stack_size;
 966             this.flags = flags;
 967             this.locals = locals;
 968             this.stack = stack;
 969             this.classHierarchy = classHierarchy;
 970         }
 971 
 972         @Override

1009         }
1010 
1011         Frame frameInExceptionHandler(int flags, Type excType) {
1012             return new Frame(offset, flags, localsSize, 1, locals, new Type[] {excType}, classHierarchy);
1013         }
1014 
1015         void initializeObject(Type old_object, Type new_object) {
1016             int i;
1017             for (i = 0; i < localsSize; i++) {
1018                 if (locals[i].equals(old_object)) {
1019                     locals[i] = new_object;
1020                     localsChanged = true;
1021                 }
1022             }
1023             for (i = 0; i < stackSize; i++) {
1024                 if (stack[i].equals(old_object)) {
1025                     stack[i] = new_object;
1026                 }
1027             }
1028             if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1029                 flags = 0;

1030             }
1031         }
1032 
1033         Frame checkLocal(int index) {
1034             if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1035             if (locals == null) {
1036                 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1037                 Arrays.fill(locals, Type.TOP_TYPE);
1038             } else if (index >= locals.length) {
1039                 int current = locals.length;
1040                 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1041                 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1042             }
1043             return this;
1044         }
1045 


















1046         private void checkStack(int index) {
1047             if (index >= frameMaxStack) frameMaxStack = index + 1;
1048             if (stack == null) {
1049                 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1050                 Arrays.fill(stack, Type.TOP_TYPE);
1051             } else if (index >= stack.length) {
1052                 int current = stack.length;
1053                 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1054                 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1055             }
1056         }
1057 
1058         private void setLocalRawInternal(int index, Type type) {
1059             checkLocal(index);
1060             localsChanged |= !type.equals(locals[index]);
1061             locals[index] = type;
1062         }
1063 
1064         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) {
1065             int localsSize = 0;
1066             // Pre-emptively create a locals array that encompass all parameter slots
1067             checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1068             Type type;
1069             Type[] locals = this.locals;
1070             if (!isStatic) {
1071                 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {



1072                     type = Type.UNITIALIZED_THIS_TYPE;
1073                     flags |= FLAG_THIS_UNINIT;
1074                 } else {


1075                     type = thisKlass;

1076                 }
1077                 locals[localsSize++] = type;
1078             }
1079             for (int i = 0; i < methodDesc.parameterCount(); i++) {
1080                 var desc = methodDesc.parameterType(i);
1081                 if (desc == CD_long) {
1082                     locals[localsSize    ] = Type.LONG_TYPE;
1083                     locals[localsSize + 1] = Type.LONG2_TYPE;
1084                     localsSize += 2;
1085                 } else if (desc == CD_double) {
1086                     locals[localsSize    ] = Type.DOUBLE_TYPE;
1087                     locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1088                     localsSize += 2;
1089                 } else {
1090                     if (!desc.isPrimitive()) {
1091                         type = Type.referenceType(desc);
1092                     } else if (desc == CD_float) {
1093                         type = Type.FLOAT_TYPE;
1094                     } else {
1095                         type = Type.INTEGER_TYPE;
1096                     }
1097                     locals[localsSize++] = type;
1098                 }
1099             }
1100             this.localsSize = localsSize;
1101         }
1102 
1103         void copyFrom(Frame src) {
1104             if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1105             localsSize = src.localsSize;
1106             checkLocal(src.localsSize - 1);
1107             if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1108             if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1109             stackSize = src.stackSize;
1110             checkStack(src.stackSize - 1);
1111             if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);


1112             flags = src.flags;
1113             localsChanged = true;
1114         }
1115 
1116         void checkAssignableTo(Frame target) {
1117             int localsSize = this.localsSize;
1118             int stackSize = this.stackSize;

1119             if (target.flags == -1) {
1120                 target.locals = locals == null ? null : locals.clone();
1121                 target.localsSize = localsSize;
1122                 if (stackSize > 0) {
1123                     target.stack = stack.clone();
1124                     target.stackSize = stackSize;
1125                 }


1126                 target.flags = flags;
1127                 target.dirty = true;
1128             } else {
1129                 if (target.localsSize > localsSize) {
1130                     target.localsSize = localsSize;
1131                     target.dirty = true;
1132                 }
1133                 for (int i = 0; i < target.localsSize; i++) {
1134                     merge(locals[i], target.locals, i, target);
1135                 }
1136                 if (stackSize != target.stackSize) {
1137                     throw generatorError("Stack size mismatch");
1138                 }
1139                 for (int i = 0; i < target.stackSize; i++) {
1140                     if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1141                         throw generatorError("Stack content mismatch");
1142                     }
1143                 }



1144             }
1145         }
1146 
1147         private Type getLocalRawInternal(int index) {
1148             checkLocal(index);
1149             return locals[index];
1150         }
1151 
1152         Type getLocal(int index) {
1153             Type ret = getLocalRawInternal(index);
1154             if (index >= localsSize) {
1155                 localsSize = index + 1;
1156             }
1157             return ret;
1158         }
1159 
1160         void setLocal(int index, Type type) {
1161             Type old = getLocalRawInternal(index);
1162             if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1163                 setLocalRawInternal(index + 1, Type.TOP_TYPE);

1180             if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1181                 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1182             }
1183             setLocalRawInternal(index, type1);
1184             setLocalRawInternal(index + 1, type2);
1185             if (index >= localsSize - 1) {
1186                 localsSize = index + 2;
1187             }
1188         }
1189 
1190         private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1191             var to = toTypes[i];
1192             var newTo = to.mergeFrom(me, classHierarchy);
1193             if (to != newTo && !to.equals(newTo)) {
1194                 toTypes[i] = newTo;
1195                 target.dirty = true;
1196             }
1197             return newTo;
1198         }
1199 













































1200         private static int trimAndCompress(Type[] types, int count) {
1201             while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1202             int compressed = 0;
1203             for (int i = 0; i < count; i++) {
1204                 if (!types[i].isCategory2_2nd()) {
1205                     if (compressed != i) {
1206                         types[compressed] = types[i];
1207                     }
1208                     compressed++;
1209                 }
1210             }
1211             return compressed;
1212         }
1213 
1214         void trimAndCompress() {
1215             localsSize = trimAndCompress(locals, localsSize);
1216             stackSize = trimAndCompress(stack, stackSize);
1217         }
1218 
1219         private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1220             if (l1 == null || l2 == null) return commonSize == 0;
1221             return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1222         }
1223 
1224         void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {











1225             int localsSize = this.localsSize;
1226             int stackSize = this.stackSize;
1227             int offsetDelta = offset - prevFrame.offset - 1;
1228             if (stackSize == 0) {
1229                 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1230                 int diffLocalsSize = localsSize - prevFrame.localsSize;
1231                 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1232                     if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame
1233                         out.writeU1(offsetDelta);
1234                     } else {   //chop, same extended or append frame
1235                         out.writeU1U2(251 + diffLocalsSize, offsetDelta);
1236                         for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1237                     }
1238                     return;
1239                 }
1240             } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1241                 if (offsetDelta < 64) {  //same locals 1 stack item frame
1242                     out.writeU1(64 + offsetDelta);
1243                 } else {  //same locals 1 stack item extended frame
1244                     out.writeU1U2(247, offsetDelta);
1245                 }
1246                 stack[0].writeTo(out, cp);
1247                 return;
1248             }
1249             //full frame
1250             out.writeU1U2U2(255, offsetDelta, localsSize);
1251             for (int i=0; i<localsSize; i++) locals[i].writeTo(out, cp);
1252             out.writeU2(stackSize);
1253             for (int i=0; i<stackSize; i++) stack[i].writeTo(out, cp);

1254         }
1255     }
1256 
1257     private static record Type(int tag, ClassDesc sym, int bci) {
1258 
1259         //singleton types
1260         static final Type TOP_TYPE = simpleType(ITEM_TOP),
1261                 NULL_TYPE = simpleType(ITEM_NULL),
1262                 INTEGER_TYPE = simpleType(ITEM_INTEGER),
1263                 FLOAT_TYPE = simpleType(ITEM_FLOAT),
1264                 LONG_TYPE = simpleType(ITEM_LONG),
1265                 LONG2_TYPE = simpleType(ITEM_LONG_2ND),
1266                 DOUBLE_TYPE = simpleType(ITEM_DOUBLE),
1267                 BOOLEAN_TYPE = simpleType(ITEM_BOOLEAN),
1268                 BYTE_TYPE = simpleType(ITEM_BYTE),
1269                 CHAR_TYPE = simpleType(ITEM_CHAR),
1270                 SHORT_TYPE = simpleType(ITEM_SHORT),
1271                 DOUBLE2_TYPE = simpleType(ITEM_DOUBLE_2ND),
1272                 UNITIALIZED_THIS_TYPE = simpleType(ITEM_UNINITIALIZED_THIS);
1273 

   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.*;





  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     private static final int ITEM_TOP = 0,
 167             ITEM_INTEGER = 1,
 168             ITEM_FLOAT = 2,
 169             ITEM_DOUBLE = 3,
 170             ITEM_LONG = 4,
 171             ITEM_NULL = 5,
 172             ITEM_UNINITIALIZED_THIS = 6,
 173             ITEM_OBJECT = 7,
 174             ITEM_UNINITIALIZED = 8,
 175             ITEM_BOOLEAN = 9,
 176             ITEM_BYTE = 10,

 178             ITEM_CHAR = 12,
 179             ITEM_LONG_2ND = 13,
 180             ITEM_DOUBLE_2ND = 14;
 181 
 182     private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
 183         Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
 184         Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
 185 
 186     static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
 187 
 188     private final Type thisType;
 189     private final String methodName;
 190     private final MethodTypeDesc methodDesc;
 191     private final RawBytecodeHelper.CodeRange bytecode;
 192     private final SplitConstantPool cp;
 193     private final boolean isStatic;
 194     private final LabelContext labelContext;
 195     private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
 196     private final List<RawExceptionCatch> rawHandlers;
 197     private final ClassHierarchyImpl classHierarchy;
 198     private final UnsetField[] strictFieldsToPut; // exact-sized, do not modify this copy!
 199     private final boolean patchDeadCode;
 200     private final boolean filterDeadLabels;
 201     private Frame[] frames = EMPTY_FRAME_ARRAY;
 202     private int framesCount = 0;
 203     private final Frame currentFrame;
 204     private int maxStack, maxLocals;
 205 
 206     /**
 207      * Primary constructor of the <code>Generator</code> class.
 208      * New <code>Generator</code> instance must be created for each individual class/method.
 209      * Instance contains only immutable results, all the calculations are processed during instance construction.
 210      *
 211      * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
 212      * labels to bytecode offsets (or vice versa)
 213      * @param thisClass class to generate stack maps for
 214      * @param methodName method name to generate stack maps for
 215      * @param methodDesc method descriptor to generate stack maps for
 216      * @param isStatic information whether the method is static
 217      * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects  and patches dead code
 218      * @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
 219      * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
 220      * and also to be altered when dead code is detected and must be excluded from exception handlers
 221      */
 222     public StackMapGenerator(LabelContext labelContext,
 223                      ClassDesc thisClass,
 224                      String methodName,
 225                      MethodTypeDesc methodDesc,
 226                      boolean isStatic,
 227                      RawBytecodeHelper.CodeRange bytecode,
 228                      SplitConstantPool cp,
 229                      ClassFileImpl context,
 230                      UnsetField[] strictFields,
 231                      List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
 232         this.thisType = Type.referenceType(thisClass);
 233         this.methodName = methodName;
 234         this.methodDesc = methodDesc;
 235         this.isStatic = isStatic;
 236         this.bytecode = bytecode;
 237         this.cp = cp;
 238         this.labelContext = labelContext;
 239         this.handlers = handlers;
 240         this.rawHandlers = new ArrayList<>(handlers.size());
 241         this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
 242         this.patchDeadCode = context.patchDeadCode();
 243         this.filterDeadLabels = context.dropDeadLabels();
 244         this.currentFrame = new Frame(classHierarchy);
 245         if (OBJECT_INITIALIZER_NAME.equals(methodName)) {
 246             this.strictFieldsToPut = strictFields;
 247         } else {
 248             this.strictFieldsToPut = UnsetField.EMPTY_ARRAY;
 249         }
 250         generate();
 251     }
 252 
 253     /**
 254      * Calculated maximum number of the locals required
 255      * @return maximum number of the locals required
 256      */
 257     public int maxLocals() {
 258         return maxLocals;
 259     }
 260 
 261     /**
 262      * Calculated maximum stack size required
 263      * @return maximum stack size required
 264      */
 265     public int maxStack() {
 266         return maxStack;
 267     }
 268 
 269     private Frame getFrame(int offset) {

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

 435         currentFrame.offset = -1;
 436         int stackmapIndex = 0;
 437         var bcs = bytecode.start();
 438         boolean ncf = false;
 439         while (bcs.next()) {
 440             currentFrame.offset = bcs.bci();
 441             if (stackmapIndex < framesCount) {
 442                 int thisOffset = frames[stackmapIndex].offset;
 443                 if (ncf && thisOffset > bcs.bci()) {
 444                     throw generatorError("Expecting a stack map frame");
 445                 }
 446                 if (thisOffset == bcs.bci()) {
 447                     Frame nextFrame = frames[stackmapIndex++];
 448                     if (!ncf) {
 449                         currentFrame.checkAssignableTo(nextFrame);
 450                     }
 451                     while (!nextFrame.dirty) { //skip unmatched frames
 452                         if (stackmapIndex == framesCount) return; //skip the rest of this round
 453                         nextFrame = frames[stackmapIndex++];
 454                     }

 757                 throw generatorError("number of keys in lookupswitch less than 0");
 758             }
 759             delta = 2;
 760             for (int i = 0; i < (keys - 1); i++) {
 761                 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
 762                 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
 763                 if (this_key >= next_key) {
 764                     throw generatorError("Bad lookupswitch instruction");
 765                 }
 766             }
 767         }
 768         int target = bci + defaultOffset;
 769         checkJumpTarget(currentFrame, target);
 770         for (int i = 0; i < keys; i++) {
 771             target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
 772             checkJumpTarget(currentFrame, target);
 773         }
 774     }
 775 
 776     private void processFieldInstructions(RawBytecodeHelper bcs) {
 777         var nameAndType = cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType();
 778         var desc = Util.fieldTypeSymbol(nameAndType.type());
 779         var currentFrame = this.currentFrame;
 780         switch (bcs.opcode()) {
 781             case GETSTATIC ->
 782                 currentFrame.pushStack(desc);
 783             case PUTSTATIC -> {
 784                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
 785             }
 786             case GETFIELD -> {
 787                 currentFrame.decStack(1);
 788                 currentFrame.pushStack(desc);
 789             }
 790             case PUTFIELD -> {
 791                 if (strictFieldsToPut.length > 0) {
 792                     currentFrame.putStrictField(nameAndType);
 793                 }
 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                     var owner = cp.entryByIndex(index, MemberRefEntry.class).owner();
 818                     if (!owner.name().equalsString(((ClassOrInterfaceDescImpl) thisType.sym).internalName())
 819                             && currentFrame.unsetFieldsSize != 0) {
 820                         throw generatorError("Unset fields mismatch");
 821                     }
 822                     currentFrame.initializeObject(type, thisType);
 823                     currentFrame.unsetFieldsSize = 0;
 824                     currentFrame.unsetFields = UnsetField.EMPTY_ARRAY;
 825                     thisUninit = true;
 826                 } else if (type.tag == ITEM_UNINITIALIZED) {
 827                     Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
 828                     if (inTryBlock) {
 829                         processExceptionHandlerTargets(bci, thisUninit);
 830                     }
 831                     currentFrame.initializeObject(type, new_class_type);
 832                 } else {
 833                     throw generatorError("Bad operand type when invoking <init>");
 834                 }
 835             } else {
 836                 currentFrame.decStack(1);
 837             }
 838         }
 839         currentFrame.pushStack(mDesc.returnType());
 840         return thisUninit;
 841     }
 842 
 843     private Type getNewarrayType(int index) {
 844         if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));

 947                 return;
 948             }
 949             if (frameOffset > offset) {
 950                 break;
 951             }
 952         }
 953         if (framesCount >= frames.length) {
 954             int newCapacity = framesCount + 8;
 955             this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
 956         }
 957         if (i != framesCount) {
 958             System.arraycopy(frames, i, frames, i + 1, framesCount - i);
 959         }
 960         frames[i] = new Frame(offset, classHierarchy);
 961         this.framesCount = framesCount + 1;
 962     }
 963 
 964     private final class Frame {
 965 
 966         int offset;
 967         int localsSize, stackSize, unsetFieldsSize;
 968         int flags;
 969         int frameMaxStack = 0, frameMaxLocals = 0;
 970         boolean dirty = false;
 971         boolean localsChanged = false;
 972 
 973         private final ClassHierarchyImpl classHierarchy;
 974 
 975         private Type[] locals, stack;
 976         private UnsetField[] unsetFields = UnsetField.EMPTY_ARRAY; // sorted, modifiable oversized array
 977 
 978         Frame(ClassHierarchyImpl classHierarchy) {
 979             this(-1, 0, 0, 0, null, null, classHierarchy);
 980         }
 981 
 982         Frame(int offset, ClassHierarchyImpl classHierarchy) {
 983             this(offset, -1, 0, 0, null, null, classHierarchy);
 984         }
 985 
 986         Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) {
 987             this.offset = offset;
 988             this.localsSize = locals_size;
 989             this.stackSize = stack_size;
 990             this.flags = flags;
 991             this.locals = locals;
 992             this.stack = stack;
 993             this.classHierarchy = classHierarchy;
 994         }
 995 
 996         @Override

1033         }
1034 
1035         Frame frameInExceptionHandler(int flags, Type excType) {
1036             return new Frame(offset, flags, localsSize, 1, locals, new Type[] {excType}, classHierarchy);
1037         }
1038 
1039         void initializeObject(Type old_object, Type new_object) {
1040             int i;
1041             for (i = 0; i < localsSize; i++) {
1042                 if (locals[i].equals(old_object)) {
1043                     locals[i] = new_object;
1044                     localsChanged = true;
1045                 }
1046             }
1047             for (i = 0; i < stackSize; i++) {
1048                 if (stack[i].equals(old_object)) {
1049                     stack[i] = new_object;
1050                 }
1051             }
1052             if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1053                 flags &= ~FLAG_THIS_UNINIT;
1054                 assert flags == 0 : flags;
1055             }
1056         }
1057 
1058         Frame checkLocal(int index) {
1059             if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1060             if (locals == null) {
1061                 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1062                 Arrays.fill(locals, Type.TOP_TYPE);
1063             } else if (index >= locals.length) {
1064                 int current = locals.length;
1065                 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1066                 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1067             }
1068             return this;
1069         }
1070 
1071         void putStrictField(NameAndTypeEntry nat) {
1072             int shift = 0;
1073             var array = unsetFields;
1074             for (int i = 0; i < unsetFieldsSize; i++) {
1075                 var f = array[i];
1076                 if (f.name().equals(nat.name()) && f.type().equals(nat.type())) {
1077                     shift++;
1078                 } else if (shift != 0) {
1079                     array[i - shift] = array[i];
1080                     array[i] = null;
1081                 }
1082             }
1083             if (shift > 1) {
1084                 throw generatorError(nat + "; discovered " + shift);
1085             }
1086             unsetFieldsSize -= shift;
1087         }
1088 
1089         private void checkStack(int index) {
1090             if (index >= frameMaxStack) frameMaxStack = index + 1;
1091             if (stack == null) {
1092                 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1093                 Arrays.fill(stack, Type.TOP_TYPE);
1094             } else if (index >= stack.length) {
1095                 int current = stack.length;
1096                 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1097                 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1098             }
1099         }
1100 
1101         private void setLocalRawInternal(int index, Type type) {
1102             checkLocal(index);
1103             localsChanged |= !type.equals(locals[index]);
1104             locals[index] = type;
1105         }
1106 
1107         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass, UnsetField[] strictFieldsToPut) {
1108             int localsSize = 0;
1109             // Pre-emptively create a locals array that encompass all parameter slots
1110             checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1111             Type type;
1112             Type[] locals = this.locals;
1113             if (!isStatic) {
1114                 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
1115                     int strictFieldCount = strictFieldsToPut.length;
1116                     this.unsetFields = UnsetField.copyArray(strictFieldsToPut, strictFieldCount);
1117                     this.unsetFieldsSize = strictFieldCount;
1118                     type = Type.UNITIALIZED_THIS_TYPE;
1119                     this.flags = FLAG_THIS_UNINIT;
1120                 } else {
1121                     this.unsetFields = UnsetField.EMPTY_ARRAY;
1122                     this.unsetFieldsSize = 0;
1123                     type = thisKlass;
1124                     this.flags = 0;
1125                 }
1126                 locals[localsSize++] = type;
1127             }
1128             for (int i = 0; i < methodDesc.parameterCount(); i++) {
1129                 var desc = methodDesc.parameterType(i);
1130                 if (desc == CD_long) {
1131                     locals[localsSize    ] = Type.LONG_TYPE;
1132                     locals[localsSize + 1] = Type.LONG2_TYPE;
1133                     localsSize += 2;
1134                 } else if (desc == CD_double) {
1135                     locals[localsSize    ] = Type.DOUBLE_TYPE;
1136                     locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1137                     localsSize += 2;
1138                 } else {
1139                     if (!desc.isPrimitive()) {
1140                         type = Type.referenceType(desc);
1141                     } else if (desc == CD_float) {
1142                         type = Type.FLOAT_TYPE;
1143                     } else {
1144                         type = Type.INTEGER_TYPE;
1145                     }
1146                     locals[localsSize++] = type;
1147                 }
1148             }
1149             this.localsSize = localsSize;
1150         }
1151 
1152         void copyFrom(Frame src) {
1153             if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1154             localsSize = src.localsSize;
1155             checkLocal(src.localsSize - 1);
1156             if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1157             if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1158             stackSize = src.stackSize;
1159             checkStack(src.stackSize - 1);
1160             if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);
1161             unsetFieldsSize = src.unsetFieldsSize;
1162             unsetFields = UnsetField.copyArray(src.unsetFields, src.unsetFieldsSize);
1163             flags = src.flags;
1164             localsChanged = true;
1165         }
1166 
1167         void checkAssignableTo(Frame target) {
1168             int localsSize = this.localsSize;
1169             int stackSize = this.stackSize;
1170             int myUnsetFieldsSize = this.unsetFieldsSize;
1171             if (target.flags == -1) {
1172                 target.locals = locals == null ? null : locals.clone();
1173                 target.localsSize = localsSize;
1174                 if (stackSize > 0) {
1175                     target.stack = stack.clone();
1176                     target.stackSize = stackSize;
1177                 }
1178                 target.unsetFields = UnsetField.copyArray(this.unsetFields, myUnsetFieldsSize);
1179                 target.unsetFieldsSize = myUnsetFieldsSize;
1180                 target.flags = flags;
1181                 target.dirty = true;
1182             } else {
1183                 if (target.localsSize > localsSize) {
1184                     target.localsSize = localsSize;
1185                     target.dirty = true;
1186                 }
1187                 for (int i = 0; i < target.localsSize; i++) {
1188                     merge(locals[i], target.locals, i, target);
1189                 }
1190                 if (stackSize != target.stackSize) {
1191                     throw generatorError("Stack size mismatch");
1192                 }
1193                 for (int i = 0; i < target.stackSize; i++) {
1194                     if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1195                         throw generatorError("Stack content mismatch");
1196                     }
1197                 }
1198                 if (myUnsetFieldsSize != 0) {
1199                     mergeUnsetFields(target);
1200                 }
1201             }
1202         }
1203 
1204         private Type getLocalRawInternal(int index) {
1205             checkLocal(index);
1206             return locals[index];
1207         }
1208 
1209         Type getLocal(int index) {
1210             Type ret = getLocalRawInternal(index);
1211             if (index >= localsSize) {
1212                 localsSize = index + 1;
1213             }
1214             return ret;
1215         }
1216 
1217         void setLocal(int index, Type type) {
1218             Type old = getLocalRawInternal(index);
1219             if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1220                 setLocalRawInternal(index + 1, Type.TOP_TYPE);

1237             if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1238                 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1239             }
1240             setLocalRawInternal(index, type1);
1241             setLocalRawInternal(index + 1, type2);
1242             if (index >= localsSize - 1) {
1243                 localsSize = index + 2;
1244             }
1245         }
1246 
1247         private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1248             var to = toTypes[i];
1249             var newTo = to.mergeFrom(me, classHierarchy);
1250             if (to != newTo && !to.equals(newTo)) {
1251                 toTypes[i] = newTo;
1252                 target.dirty = true;
1253             }
1254             return newTo;
1255         }
1256 
1257         // Merge this frame's unset fields into the target frame
1258         private void mergeUnsetFields(Frame target) {
1259             int myUnsetSize = unsetFieldsSize;
1260             int targetUnsetSize = target.unsetFieldsSize;
1261             var myUnsets = unsetFields;
1262             var targetUnsets = target.unsetFields;
1263             if (!UnsetField.mismatches(myUnsets, myUnsetSize, targetUnsets, targetUnsetSize)) {
1264                 return; // no merge
1265             }
1266             // merge sort
1267             var merged = new UnsetField[StackMapGenerator.this.strictFieldsToPut.length];
1268             int mergedSize = 0;
1269             int i = 0;
1270             int j = 0;
1271             while (i < myUnsetSize && j < targetUnsetSize) {
1272                 var myCandidate = myUnsets[i];
1273                 var targetCandidate = targetUnsets[j];
1274                 var cmp = myCandidate.compareTo(targetCandidate);
1275                 if (cmp == 0) {
1276                     merged[mergedSize++] = myCandidate;
1277                     i++;
1278                     j++;
1279                 } else if (cmp < 0) {
1280                     merged[mergedSize++] = myCandidate;
1281                     i++;
1282                 } else {
1283                     merged[mergedSize++] = targetCandidate;
1284                     j++;
1285                 }
1286             }
1287             if (i < myUnsetSize) {
1288                 int len = myUnsetSize - i;
1289                 System.arraycopy(myUnsets, i, merged, mergedSize, len);
1290                 mergedSize += len;
1291             } else if (j < targetUnsetSize) {
1292                 int len = targetUnsetSize - j;
1293                 System.arraycopy(targetUnsets, j, merged, mergedSize, len);
1294                 mergedSize += len;
1295             }
1296 
1297             target.unsetFieldsSize = mergedSize;
1298             target.unsetFields = merged;
1299             target.dirty = true;
1300         }
1301 
1302         private static int trimAndCompress(Type[] types, int count) {
1303             while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1304             int compressed = 0;
1305             for (int i = 0; i < count; i++) {
1306                 if (!types[i].isCategory2_2nd()) {
1307                     if (compressed != i) {
1308                         types[compressed] = types[i];
1309                     }
1310                     compressed++;
1311                 }
1312             }
1313             return compressed;
1314         }
1315 
1316         void trimAndCompress() {
1317             localsSize = trimAndCompress(locals, localsSize);
1318             stackSize = trimAndCompress(stack, stackSize);
1319         }
1320 
1321         private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1322             if (l1 == null || l2 == null) return commonSize == 0;
1323             return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1324         }
1325 
1326         int writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
1327             int extraFrames = 0;
1328             if (UnsetField.mismatches(unsetFields, unsetFieldsSize, prevFrame.unsetFields, prevFrame.unsetFieldsSize)) {
1329                 // Emit unset_fields frame
1330                 out.writeU1U2(StackMapDecoder.ASSERT_UNSET_FIELDS, unsetFieldsSize);
1331                 var array = unsetFields;
1332                 for (int i = 0; i < unsetFieldsSize; i++) {
1333                     var f = array[i];
1334                     out.writeIndex(cp.nameAndTypeEntry(f.name(), f.type()));
1335                 }
1336                 extraFrames++;
1337             }
1338             int localsSize = this.localsSize;
1339             int stackSize = this.stackSize;
1340             int offsetDelta = offset - prevFrame.offset - 1;
1341             if (stackSize == 0) {
1342                 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1343                 int diffLocalsSize = localsSize - prevFrame.localsSize;
1344                 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1345                     if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame
1346                         out.writeU1(offsetDelta);
1347                     } else {   //chop, same extended or append frame
1348                         out.writeU1U2(251 + diffLocalsSize, offsetDelta);
1349                         for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1350                     }
1351                     return extraFrames;
1352                 }
1353             } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1354                 if (offsetDelta < 64) {  //same locals 1 stack item frame
1355                     out.writeU1(64 + offsetDelta);
1356                 } else {  //same locals 1 stack item extended frame
1357                     out.writeU1U2(247, offsetDelta);
1358                 }
1359                 stack[0].writeTo(out, cp);
1360                 return extraFrames;
1361             }
1362             //full frame
1363             out.writeU1U2U2(255, offsetDelta, localsSize);
1364             for (int i=0; i<localsSize; i++) locals[i].writeTo(out, cp);
1365             out.writeU2(stackSize);
1366             for (int i=0; i<stackSize; i++) stack[i].writeTo(out, cp);
1367             return extraFrames;
1368         }
1369     }
1370 
1371     private static record Type(int tag, ClassDesc sym, int bci) {
1372 
1373         //singleton types
1374         static final Type TOP_TYPE = simpleType(ITEM_TOP),
1375                 NULL_TYPE = simpleType(ITEM_NULL),
1376                 INTEGER_TYPE = simpleType(ITEM_INTEGER),
1377                 FLOAT_TYPE = simpleType(ITEM_FLOAT),
1378                 LONG_TYPE = simpleType(ITEM_LONG),
1379                 LONG2_TYPE = simpleType(ITEM_LONG_2ND),
1380                 DOUBLE_TYPE = simpleType(ITEM_DOUBLE),
1381                 BOOLEAN_TYPE = simpleType(ITEM_BOOLEAN),
1382                 BYTE_TYPE = simpleType(ITEM_BYTE),
1383                 CHAR_TYPE = simpleType(ITEM_CHAR),
1384                 SHORT_TYPE = simpleType(ITEM_SHORT),
1385                 DOUBLE2_TYPE = simpleType(ITEM_DOUBLE_2ND),
1386                 UNITIALIZED_THIS_TYPE = simpleType(ITEM_UNINITIALIZED_THIS);
1387 
< prev index next >