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
|