13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 *
26 */
27 package jdk.internal.classfile.impl;
28
29 import java.lang.classfile.Attribute;
30 import java.lang.classfile.Attributes;
31 import java.lang.classfile.Label;
32 import java.lang.classfile.attribute.StackMapTableAttribute;
33 import java.lang.classfile.constantpool.ClassEntry;
34 import java.lang.classfile.constantpool.ConstantDynamicEntry;
35 import java.lang.classfile.constantpool.ConstantPoolBuilder;
36 import java.lang.classfile.constantpool.InvokeDynamicEntry;
37 import java.lang.classfile.constantpool.MemberRefEntry;
38 import java.lang.classfile.constantpool.Utf8Entry;
39 import java.lang.constant.ClassDesc;
40 import java.lang.constant.MethodTypeDesc;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.List;
44 import java.util.Objects;
45 import java.util.stream.Collectors;
46
47 import jdk.internal.constant.ClassOrInterfaceDescImpl;
48 import jdk.internal.util.Preconditions;
49
50 import static java.lang.classfile.ClassFile.*;
51 import static java.lang.classfile.constantpool.PoolEntry.*;
52 import static java.lang.constant.ConstantDescs.*;
53 import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
54
55 /**
56 * StackMapGenerator is responsible for stack map frames generation.
57 * <p>
58 * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.
59 * <p>
60 * The {@linkplain #generate() frames computation} consists of following steps:
61 * <ol>
62 * <li>{@linkplain #detectFrames() Detection} of mandatory stack map frames:<ul>
63 * <li>Mandatory stack map frame include all jump and switch instructions targets,
64 * offsets immediately following {@linkplain #noControlFlow(int) "no control flow"}
65 * and all exception table handlers.
66 * <li>Detection is performed in a single fast pass through the bytecode,
140 * Focus of the whole algorithm is on high performance and low memory footprint:<ul>
141 * <li>It does not produce, collect nor visit any complex intermediate structures
142 * <i>(beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}).</i>
143 * <li>It works with only minimal mandatory stack map frames.
144 * <li>It does not spend time on any non-essential verifications.
145 * </ul>
146 */
147
148 public final class StackMapGenerator {
149
150 static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) {
151 return new StackMapGenerator(
152 dcb,
153 buf.thisClass().asSymbol(),
154 dcb.methodInfo.methodName().stringValue(),
155 dcb.methodInfo.methodTypeSymbol(),
156 (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0,
157 dcb.bytecodesBufWriter.bytecodeView(),
158 dcb.constantPool,
159 dcb.context,
160 dcb.handlers);
161 }
162
163 private static final String OBJECT_INITIALIZER_NAME = "<init>";
164 private static final int FLAG_THIS_UNINIT = 0x01;
165 private static final int FRAME_DEFAULT_CAPACITY = 10;
166 private static final int T_BOOLEAN = 4, T_LONG = 11;
167 private static final Frame[] EMPTY_FRAME_ARRAY = {};
168
169 public static final int
170 ITEM_TOP = 0,
171 ITEM_INTEGER = 1,
172 ITEM_FLOAT = 2,
173 ITEM_DOUBLE = 3,
174 ITEM_LONG = 4,
175 ITEM_NULL = 5,
176 ITEM_UNINITIALIZED_THIS = 6,
177 ITEM_OBJECT = 7,
178 ITEM_UNINITIALIZED = 8,
179 ITEM_BOOLEAN = 9,
180 ITEM_BYTE = 10,
181 ITEM_SHORT = 11,
182 ITEM_CHAR = 12,
183 ITEM_LONG_2ND = 13,
184 ITEM_DOUBLE_2ND = 14,
185 ITEM_BOGUS = -1;
186
187 // Ranges represented by these constants are inclusive on both ends
188 public static final int
189 SAME_FRAME_END = 63,
190 SAME_LOCALS_1_STACK_ITEM_FRAME_START = 64,
191 SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127,
192 RESERVED_END = 246,
193 SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
194 CHOP_FRAME_START = 248,
195 CHOP_FRAME_END = 250,
196 SAME_FRAME_EXTENDED = 251,
197 APPEND_FRAME_START = 252,
198 APPEND_FRAME_END = 254,
199 FULL_FRAME = 255;
200
201 private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
202 Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
203 Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
204
205 static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
206
207 private final Type thisType;
208 private final String methodName;
209 private final MethodTypeDesc methodDesc;
210 private final RawBytecodeHelper.CodeRange bytecode;
211 private final SplitConstantPool cp;
212 private final boolean isStatic;
213 private final LabelContext labelContext;
214 private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
215 private final List<RawExceptionCatch> rawHandlers;
216 private final ClassHierarchyImpl classHierarchy;
217 private final boolean patchDeadCode;
218 private Frame[] frames = EMPTY_FRAME_ARRAY;
219 private int framesCount = 0;
220 private final Frame currentFrame;
221 private int maxStack, maxLocals;
222
223 /**
224 * Primary constructor of the <code>Generator</code> class.
225 * New <code>Generator</code> instance must be created for each individual class/method.
226 * Instance contains only immutable results, all the calculations are processed during instance construction.
227 *
228 * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
229 * labels to bytecode offsets (or vice versa)
230 * @param thisClass class to generate stack maps for
231 * @param methodName method name to generate stack maps for
232 * @param methodDesc method descriptor to generate stack maps for
233 * @param isStatic information whether the method is static
234 * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects and patches dead code
235 * @param cp R/W <code>ConstantPoolBuilder</code> instance used to resolve all involved CP entries and also generate new entries referenced from the generated stack maps
236 * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
237 * and also to be altered when dead code is detected and must be excluded from exception handlers
238 */
239 public StackMapGenerator(LabelContext labelContext,
240 ClassDesc thisClass,
241 String methodName,
242 MethodTypeDesc methodDesc,
243 boolean isStatic,
244 RawBytecodeHelper.CodeRange bytecode,
245 SplitConstantPool cp,
246 ClassFileImpl context,
247 List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
248 this.thisType = Type.referenceType(thisClass);
249 this.methodName = methodName;
250 this.methodDesc = methodDesc;
251 this.isStatic = isStatic;
252 this.bytecode = bytecode;
253 this.cp = cp;
254 this.labelContext = labelContext;
255 this.handlers = handlers;
256 this.rawHandlers = new ArrayList<>(handlers.size());
257 this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
258 this.patchDeadCode = context.patchDeadCode();
259 this.currentFrame = new Frame(classHierarchy);
260 generate();
261 }
262
263 /**
264 * Calculated maximum number of the locals required
265 * @return maximum number of the locals required
266 */
267 public int maxLocals() {
268 return maxLocals;
269 }
270
271 /**
272 * Calculated maximum stack size required
273 * @return maximum stack size required
274 */
275 public int maxStack() {
276 return maxStack;
277 }
278
279 private Frame getFrame(int offset) {
388 } else {
389 //split
390 Label newStart = labelContext.newLabel();
391 labelContext.setLabelTarget(newStart, rangeEnd);
392 Label newEnd = labelContext.newLabel();
393 labelContext.setLabelTarget(newEnd, rangeStart);
394 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
395 it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
396 }
397 }
398 }
399
400 /**
401 * Getter of the generated <code>StackMapTableAttribute</code> or null if stack map is empty
402 * @return <code>StackMapTableAttribute</code> or null if stack map is empty
403 */
404 public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
405 return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
406 @Override
407 public void writeBody(BufWriterImpl b) {
408 b.writeU2(framesCount);
409 Frame prevFrame = new Frame(classHierarchy);
410 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
411 prevFrame.trimAndCompress();
412 for (int i = 0; i < framesCount; i++) {
413 var fr = frames[i];
414 fr.trimAndCompress();
415 fr.writeTo(b, prevFrame, cp);
416 prevFrame = fr;
417 }
418 }
419
420 @Override
421 public Utf8Entry attributeName() {
422 return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
423 }
424 };
425 }
426
427 private static Type cpIndexToType(int index, ConstantPoolBuilder cp) {
428 return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol());
429 }
430
431 private void processMethod() {
432 var frames = this.frames;
433 var currentFrame = this.currentFrame;
434 currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
435 currentFrame.stackSize = 0;
436 currentFrame.flags = 0;
437 currentFrame.offset = -1;
438 int stackmapIndex = 0;
439 var bcs = bytecode.start();
440 boolean ncf = false;
441 while (bcs.next()) {
442 currentFrame.offset = bcs.bci();
443 if (stackmapIndex < framesCount) {
444 int thisOffset = frames[stackmapIndex].offset;
445 if (ncf && thisOffset > bcs.bci()) {
446 throw generatorError("Expecting a stack map frame");
447 }
448 if (thisOffset == bcs.bci()) {
449 Frame nextFrame = frames[stackmapIndex++];
450 if (!ncf) {
451 currentFrame.checkAssignableTo(nextFrame);
452 }
453 while (!nextFrame.dirty) { //skip unmatched frames
454 if (stackmapIndex == framesCount) return; //skip the rest of this round
455 nextFrame = frames[stackmapIndex++];
456 }
459 currentFrame.offset = bcs.bci();
460 currentFrame.copyFrom(nextFrame);
461 nextFrame.dirty = false;
462 } else if (thisOffset < bcs.bci()) {
463 throw generatorError("Bad stack map offset");
464 }
465 } else if (ncf) {
466 throw generatorError("Expecting a stack map frame");
467 }
468 ncf = processBlock(bcs);
469 }
470 }
471
472 private boolean processBlock(RawBytecodeHelper bcs) {
473 int opcode = bcs.opcode();
474 boolean ncf = false;
475 boolean this_uninit = false;
476 boolean verified_exc_handlers = false;
477 int bci = bcs.bci();
478 Type type1, type2, type3, type4;
479 if (RawBytecodeHelper.isStoreIntoLocal(opcode) && bci >= exMin && bci < exMax) {
480 processExceptionHandlerTargets(bci, this_uninit);
481 verified_exc_handlers = true;
482 }
483 switch (opcode) {
484 case NOP -> {}
485 case RETURN -> {
486 ncf = true;
487 }
488 case ACONST_NULL ->
489 currentFrame.pushStack(Type.NULL_TYPE);
490 case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH ->
491 currentFrame.pushStack(Type.INTEGER_TYPE);
492 case LCONST_0, LCONST_1 ->
493 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
494 case FCONST_0, FCONST_1, FCONST_2 ->
495 currentFrame.pushStack(Type.FLOAT_TYPE);
496 case DCONST_0, DCONST_1 ->
497 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
498 case LDC ->
499 processLdc(bcs.getIndexU1());
682 type1 = cpIndexToType(bcs.getIndexU2(), cp);
683 int dim = bcs.getU1Unchecked(bcs.bci() + 3);
684 for (int i = 0; i < dim; i++) {
685 currentFrame.popStack();
686 }
687 currentFrame.pushStack(type1);
688 }
689 case JSR, JSR_W, RET ->
690 throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0");
691 default ->
692 throw generatorError(String.format("Bad instruction: %02x", opcode));
693 }
694 if (!verified_exc_handlers && bci >= exMin && bci < exMax) {
695 processExceptionHandlerTargets(bci, this_uninit);
696 }
697 return ncf;
698 }
699
700 private void processExceptionHandlerTargets(int bci, boolean this_uninit) {
701 for (var ex : rawHandlers) {
702 if (bci == ex.start || (currentFrame.localsChanged && bci > ex.start && bci < ex.end)) {
703 int flags = currentFrame.flags;
704 if (this_uninit) flags |= FLAG_THIS_UNINIT;
705 Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType);
706 checkJumpTarget(newFrame, ex.handler);
707 }
708 }
709 currentFrame.localsChanged = false;
710 }
711
712 private void processLdc(int index) {
713 switch (cp.entryByIndex(index).tag()) {
714 case TAG_UTF8 ->
715 currentFrame.pushStack(Type.OBJECT_TYPE);
716 case TAG_STRING ->
717 currentFrame.pushStack(Type.STRING_TYPE);
718 case TAG_CLASS ->
719 currentFrame.pushStack(Type.CLASS_TYPE);
720 case TAG_INTEGER ->
721 currentFrame.pushStack(Type.INTEGER_TYPE);
722 case TAG_FLOAT ->
723 currentFrame.pushStack(Type.FLOAT_TYPE);
724 case TAG_DOUBLE ->
725 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
726 case TAG_LONG ->
727 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
728 case TAG_METHOD_HANDLE ->
729 currentFrame.pushStack(Type.METHOD_HANDLE_TYPE);
759 throw generatorError("number of keys in lookupswitch less than 0");
760 }
761 delta = 2;
762 for (int i = 0; i < (keys - 1); i++) {
763 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
764 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
765 if (this_key >= next_key) {
766 throw generatorError("Bad lookupswitch instruction");
767 }
768 }
769 }
770 int target = bci + defaultOffset;
771 checkJumpTarget(currentFrame, target);
772 for (int i = 0; i < keys; i++) {
773 target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
774 checkJumpTarget(currentFrame, target);
775 }
776 }
777
778 private void processFieldInstructions(RawBytecodeHelper bcs) {
779 var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).type());
780 var currentFrame = this.currentFrame;
781 switch (bcs.opcode()) {
782 case GETSTATIC ->
783 currentFrame.pushStack(desc);
784 case PUTSTATIC -> {
785 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
786 }
787 case GETFIELD -> {
788 currentFrame.decStack(1);
789 currentFrame.pushStack(desc);
790 }
791 case PUTFIELD -> {
792 currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
793 }
794 default -> throw new AssertionError("Should not reach here");
795 }
796 }
797
798 private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) {
799 int index = bcs.getIndexU2();
800 int opcode = bcs.opcode();
801 var nameAndType = opcode == INVOKEDYNAMIC
802 ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType()
803 : cp.entryByIndex(index, MemberRefEntry.class).nameAndType();
804 var mDesc = Util.methodTypeSymbol(nameAndType.type());
805 int bci = bcs.bci();
806 var currentFrame = this.currentFrame;
807 currentFrame.decStack(Util.parameterSlots(mDesc));
808 if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) {
809 if (nameAndType.name().equalsString(OBJECT_INITIALIZER_NAME)) {
810 Type type = currentFrame.popStack();
811 if (type == Type.UNITIALIZED_THIS_TYPE) {
812 if (inTryBlock) {
813 processExceptionHandlerTargets(bci, true);
814 }
815 currentFrame.initializeObject(type, thisType);
816 thisUninit = true;
817 } else if (type.tag == ITEM_UNINITIALIZED) {
818 Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
819 if (inTryBlock) {
820 processExceptionHandlerTargets(bci, thisUninit);
821 }
822 currentFrame.initializeObject(type, new_class_type);
823 } else {
824 throw generatorError("Bad operand type when invoking <init>");
825 }
826 } else {
827 currentFrame.decStack(1);
828 }
829 }
830 currentFrame.pushStack(mDesc.returnType());
831 return thisUninit;
832 }
833
834 private Type getNewarrayType(int index) {
835 if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));
937 return;
938 }
939 if (frameOffset > offset) {
940 break;
941 }
942 }
943 if (framesCount >= frames.length) {
944 int newCapacity = framesCount + 8;
945 this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
946 }
947 if (i != framesCount) {
948 System.arraycopy(frames, i, frames, i + 1, framesCount - i);
949 }
950 frames[i] = new Frame(offset, classHierarchy);
951 this.framesCount = framesCount + 1;
952 }
953
954 private final class Frame {
955
956 int offset;
957 int localsSize, stackSize;
958 int flags;
959 int frameMaxStack = 0, frameMaxLocals = 0;
960 boolean dirty = false;
961 boolean localsChanged = false;
962
963 private final ClassHierarchyImpl classHierarchy;
964
965 private Type[] locals, stack;
966
967 Frame(ClassHierarchyImpl classHierarchy) {
968 this(-1, 0, 0, 0, null, null, classHierarchy);
969 }
970
971 Frame(int offset, ClassHierarchyImpl classHierarchy) {
972 this(offset, -1, 0, 0, null, null, classHierarchy);
973 }
974
975 Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) {
976 this.offset = offset;
977 this.localsSize = locals_size;
978 this.stackSize = stack_size;
979 this.flags = flags;
980 this.locals = locals;
981 this.stack = stack;
982 this.classHierarchy = classHierarchy;
983 }
984
985 @Override
986 public String toString() {
987 return (dirty ? "frame* @" : "frame @") + offset + " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) + " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize));
988 }
989
990 Frame pushStack(ClassDesc desc) {
991 if (desc == CD_long) return pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
992 if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
993 return desc == CD_void ? this
994 : pushStack(
995 desc.isPrimitive()
996 ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
997 : Type.referenceType(desc));
998 }
999
1000 Frame pushStack(Type type) {
1001 checkStack(stackSize);
1002 stack[stackSize++] = type;
1003 return this;
1004 }
1005
1006 Frame pushStack(Type type1, Type type2) {
1007 checkStack(stackSize + 1);
1008 stack[stackSize++] = type1;
1009 stack[stackSize++] = type2;
1010 return this;
1011 }
1012
1013 Type popStack() {
1014 if (stackSize < 1) throw generatorError("Operand stack underflow");
1015 return stack[--stackSize];
1016 }
1017
1018 Frame decStack(int size) {
1019 stackSize -= size;
1020 if (stackSize < 0) throw generatorError("Operand stack underflow");
1021 return this;
1022 }
1023
1024 Frame frameInExceptionHandler(int flags, Type excType) {
1025 return new Frame(offset, flags, localsSize, 1, locals, new Type[] {excType}, classHierarchy);
1026 }
1027
1028 void initializeObject(Type old_object, Type new_object) {
1029 int i;
1030 for (i = 0; i < localsSize; i++) {
1031 if (locals[i].equals(old_object)) {
1032 locals[i] = new_object;
1033 localsChanged = true;
1034 }
1035 }
1036 for (i = 0; i < stackSize; i++) {
1037 if (stack[i].equals(old_object)) {
1038 stack[i] = new_object;
1039 }
1040 }
1041 if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1042 flags = 0;
1043 }
1044 }
1045
1046 Frame checkLocal(int index) {
1047 if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1048 if (locals == null) {
1049 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1050 Arrays.fill(locals, Type.TOP_TYPE);
1051 } else if (index >= locals.length) {
1052 int current = locals.length;
1053 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1054 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1055 }
1056 return this;
1057 }
1058
1059 private void checkStack(int index) {
1060 if (index >= frameMaxStack) frameMaxStack = index + 1;
1061 if (stack == null) {
1062 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1063 Arrays.fill(stack, Type.TOP_TYPE);
1064 } else if (index >= stack.length) {
1065 int current = stack.length;
1066 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1067 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1068 }
1069 }
1070
1071 private void setLocalRawInternal(int index, Type type) {
1072 checkLocal(index);
1073 localsChanged |= !type.equals(locals[index]);
1074 locals[index] = type;
1075 }
1076
1077 void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) {
1078 int localsSize = 0;
1079 // Pre-emptively create a locals array that encompass all parameter slots
1080 checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1081 Type type;
1082 Type[] locals = this.locals;
1083 if (!isStatic) {
1084 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
1085 type = Type.UNITIALIZED_THIS_TYPE;
1086 flags |= FLAG_THIS_UNINIT;
1087 } else {
1088 type = thisKlass;
1089 }
1090 locals[localsSize++] = type;
1091 }
1092 for (int i = 0; i < methodDesc.parameterCount(); i++) {
1093 var desc = methodDesc.parameterType(i);
1094 if (desc == CD_long) {
1095 locals[localsSize ] = Type.LONG_TYPE;
1096 locals[localsSize + 1] = Type.LONG2_TYPE;
1097 localsSize += 2;
1098 } else if (desc == CD_double) {
1099 locals[localsSize ] = Type.DOUBLE_TYPE;
1100 locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1101 localsSize += 2;
1102 } else {
1103 if (!desc.isPrimitive()) {
1104 type = Type.referenceType(desc);
1105 } else if (desc == CD_float) {
1106 type = Type.FLOAT_TYPE;
1107 } else {
1108 type = Type.INTEGER_TYPE;
1109 }
1110 locals[localsSize++] = type;
1111 }
1112 }
1113 if (locals != null && localsSize < locals.length) {
1114 Arrays.fill(locals, localsSize, locals.length, Type.TOP_TYPE);
1115 }
1116 this.localsSize = localsSize;
1117 }
1118
1119 void copyFrom(Frame src) {
1120 if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1121 localsSize = src.localsSize;
1122 checkLocal(src.localsSize - 1);
1123 if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1124 if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1125 stackSize = src.stackSize;
1126 checkStack(src.stackSize - 1);
1127 if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);
1128 flags = src.flags;
1129 localsChanged = true;
1130 }
1131
1132 void checkAssignableTo(Frame target) {
1133 int localsSize = this.localsSize;
1134 int stackSize = this.stackSize;
1135 if (target.flags == -1) {
1136 target.locals = locals == null ? null : locals.clone();
1137 target.localsSize = localsSize;
1138 if (stackSize > 0) {
1139 target.stack = stack.clone();
1140 target.stackSize = stackSize;
1141 }
1142 target.flags = flags;
1143 target.dirty = true;
1144 } else {
1145 if (target.localsSize > localsSize) {
1146 target.localsSize = localsSize;
1147 target.dirty = true;
1148 }
1149 for (int i = 0; i < target.localsSize; i++) {
1150 merge(locals[i], target.locals, i, target);
1151 }
1152 if (stackSize != target.stackSize) {
1153 throw generatorError("Stack size mismatch");
1154 }
1155 for (int i = 0; i < target.stackSize; i++) {
1156 if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1157 throw generatorError("Stack content mismatch");
1158 }
1159 }
1160 }
1161 }
1162
1163 private Type getLocalRawInternal(int index) {
1164 checkLocal(index);
1165 return locals[index];
1166 }
1167
1168 Type getLocal(int index) {
1169 Type ret = getLocalRawInternal(index);
1170 if (index >= localsSize) {
1171 localsSize = index + 1;
1172 }
1173 return ret;
1174 }
1175
1176 void setLocal(int index, Type type) {
1177 Type old = getLocalRawInternal(index);
1178 if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1179 setLocalRawInternal(index + 1, Type.TOP_TYPE);
1196 if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1197 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1198 }
1199 setLocalRawInternal(index, type1);
1200 setLocalRawInternal(index + 1, type2);
1201 if (index >= localsSize - 1) {
1202 localsSize = index + 2;
1203 }
1204 }
1205
1206 private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1207 var to = toTypes[i];
1208 var newTo = to.mergeFrom(me, classHierarchy);
1209 if (to != newTo && !to.equals(newTo)) {
1210 toTypes[i] = newTo;
1211 target.dirty = true;
1212 }
1213 return newTo;
1214 }
1215
1216 private static int trimAndCompress(Type[] types, int count) {
1217 while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1218 int compressed = 0;
1219 for (int i = 0; i < count; i++) {
1220 if (!types[i].isCategory2_2nd()) {
1221 if (compressed != i) {
1222 types[compressed] = types[i];
1223 }
1224 compressed++;
1225 }
1226 }
1227 return compressed;
1228 }
1229
1230 void trimAndCompress() {
1231 localsSize = trimAndCompress(locals, localsSize);
1232 stackSize = trimAndCompress(stack, stackSize);
1233 }
1234
1235 private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1236 if (l1 == null || l2 == null) return commonSize == 0;
1237 return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1238 }
1239
1240 void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
1241 int localsSize = this.localsSize;
1242 int stackSize = this.stackSize;
1243 int offsetDelta = offset - prevFrame.offset - 1;
1244 if (stackSize == 0) {
1245 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1246 int diffLocalsSize = localsSize - prevFrame.localsSize;
1247 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1248 if (diffLocalsSize == 0 && offsetDelta <= SAME_FRAME_END) { //same frame
1249 out.writeU1(offsetDelta);
1250 } else { //chop, same extended or append frame
1251 out.writeU1U2(SAME_FRAME_EXTENDED + diffLocalsSize, offsetDelta);
1252 for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1253 }
1254 return;
1255 }
1256 } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1257 if (offsetDelta <= SAME_LOCALS_1_STACK_ITEM_FRAME_END - SAME_LOCALS_1_STACK_ITEM_FRAME_START) { //same locals 1 stack item frame
1258 out.writeU1(SAME_LOCALS_1_STACK_ITEM_FRAME_START + offsetDelta);
1259 } else { //same locals 1 stack item extended frame
1260 out.writeU1U2(SAME_LOCALS_1_STACK_ITEM_EXTENDED, offsetDelta);
|
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 *
26 */
27 package jdk.internal.classfile.impl;
28
29 import java.lang.classfile.Attribute;
30 import java.lang.classfile.Attributes;
31 import java.lang.classfile.Label;
32 import java.lang.classfile.attribute.StackMapTableAttribute;
33 import java.lang.classfile.constantpool.*;
34 import java.lang.constant.ClassDesc;
35 import java.lang.constant.MethodTypeDesc;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.stream.Collectors;
41
42 import jdk.internal.classfile.impl.WritableField.UnsetField;
43 import jdk.internal.constant.ClassOrInterfaceDescImpl;
44 import jdk.internal.util.Preconditions;
45
46 import static java.lang.classfile.ClassFile.*;
47 import static java.lang.classfile.constantpool.PoolEntry.*;
48 import static java.lang.constant.ConstantDescs.*;
49 import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
50
51 /**
52 * StackMapGenerator is responsible for stack map frames generation.
53 * <p>
54 * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.
55 * <p>
56 * The {@linkplain #generate() frames computation} consists of following steps:
57 * <ol>
58 * <li>{@linkplain #detectFrames() Detection} of mandatory stack map frames:<ul>
59 * <li>Mandatory stack map frame include all jump and switch instructions targets,
60 * offsets immediately following {@linkplain #noControlFlow(int) "no control flow"}
61 * and all exception table handlers.
62 * <li>Detection is performed in a single fast pass through the bytecode,
136 * Focus of the whole algorithm is on high performance and low memory footprint:<ul>
137 * <li>It does not produce, collect nor visit any complex intermediate structures
138 * <i>(beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}).</i>
139 * <li>It works with only minimal mandatory stack map frames.
140 * <li>It does not spend time on any non-essential verifications.
141 * </ul>
142 */
143
144 public final class StackMapGenerator {
145
146 static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) {
147 return new StackMapGenerator(
148 dcb,
149 buf.thisClass().asSymbol(),
150 dcb.methodInfo.methodName().stringValue(),
151 dcb.methodInfo.methodTypeSymbol(),
152 (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0,
153 dcb.bytecodesBufWriter.bytecodeView(),
154 dcb.constantPool,
155 dcb.context,
156 buf.getStrictInstanceFields(),
157 dcb.handlers);
158 }
159
160 private static final String OBJECT_INITIALIZER_NAME = "<init>";
161 private static final int FLAG_THIS_UNINIT = 0x01;
162 private static final int FRAME_DEFAULT_CAPACITY = 10;
163 private static final int T_BOOLEAN = 4, T_LONG = 11;
164 private static final Frame[] EMPTY_FRAME_ARRAY = {};
165
166 public static final int
167 ITEM_TOP = 0,
168 ITEM_INTEGER = 1,
169 ITEM_FLOAT = 2,
170 ITEM_DOUBLE = 3,
171 ITEM_LONG = 4,
172 ITEM_NULL = 5,
173 ITEM_UNINITIALIZED_THIS = 6,
174 ITEM_OBJECT = 7,
175 ITEM_UNINITIALIZED = 8,
176 ITEM_BOOLEAN = 9,
177 ITEM_BYTE = 10,
178 ITEM_SHORT = 11,
179 ITEM_CHAR = 12,
180 ITEM_LONG_2ND = 13,
181 ITEM_DOUBLE_2ND = 14,
182 ITEM_BOGUS = -1;
183
184 // Ranges represented by these constants are inclusive on both ends
185 public static final int
186 SAME_FRAME_END = 63,
187 SAME_LOCALS_1_STACK_ITEM_FRAME_START = 64,
188 SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127,
189 RESERVED_END = 245,
190 EARLY_LARVAL = 246,
191 SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
192 CHOP_FRAME_START = 248,
193 CHOP_FRAME_END = 250,
194 SAME_FRAME_EXTENDED = 251,
195 APPEND_FRAME_START = 252,
196 APPEND_FRAME_END = 254,
197 FULL_FRAME = 255;
198
199 private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
200 Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
201 Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
202
203 static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
204
205 private final Type thisType;
206 private final String methodName;
207 private final MethodTypeDesc methodDesc;
208 private final RawBytecodeHelper.CodeRange bytecode;
209 private final SplitConstantPool cp;
210 private final boolean isStatic;
211 private final LabelContext labelContext;
212 private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
213 private final List<RawExceptionCatch> rawHandlers;
214 private final ClassHierarchyImpl classHierarchy;
215 private final UnsetField[] strictFieldsToPut; // exact-sized, do not modify this copy!
216 private final boolean patchDeadCode;
217 private Frame[] frames = EMPTY_FRAME_ARRAY;
218 private int framesCount = 0;
219 private final Frame currentFrame;
220 private int maxStack, maxLocals;
221
222 /**
223 * Primary constructor of the <code>Generator</code> class.
224 * New <code>Generator</code> instance must be created for each individual class/method.
225 * Instance contains only immutable results, all the calculations are processed during instance construction.
226 *
227 * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
228 * labels to bytecode offsets (or vice versa)
229 * @param thisClass class to generate stack maps for
230 * @param methodName method name to generate stack maps for
231 * @param methodDesc method descriptor to generate stack maps for
232 * @param isStatic information whether the method is static
233 * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects and patches dead code
234 * @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
235 * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
236 * and also to be altered when dead code is detected and must be excluded from exception handlers
237 */
238 public StackMapGenerator(LabelContext labelContext,
239 ClassDesc thisClass,
240 String methodName,
241 MethodTypeDesc methodDesc,
242 boolean isStatic,
243 RawBytecodeHelper.CodeRange bytecode,
244 SplitConstantPool cp,
245 ClassFileImpl context,
246 UnsetField[] strictFields,
247 List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
248 this.thisType = Type.referenceType(thisClass);
249 this.methodName = methodName;
250 this.methodDesc = methodDesc;
251 this.isStatic = isStatic;
252 this.bytecode = bytecode;
253 this.cp = cp;
254 this.labelContext = labelContext;
255 this.handlers = handlers;
256 this.rawHandlers = new ArrayList<>(handlers.size());
257 this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
258 this.patchDeadCode = context.patchDeadCode();
259 this.currentFrame = new Frame(classHierarchy);
260 if (OBJECT_INITIALIZER_NAME.equals(methodName)) {
261 this.strictFieldsToPut = strictFields;
262 } else {
263 this.strictFieldsToPut = UnsetField.EMPTY_ARRAY;
264 }
265 generate();
266 }
267
268 /**
269 * Calculated maximum number of the locals required
270 * @return maximum number of the locals required
271 */
272 public int maxLocals() {
273 return maxLocals;
274 }
275
276 /**
277 * Calculated maximum stack size required
278 * @return maximum stack size required
279 */
280 public int maxStack() {
281 return maxStack;
282 }
283
284 private Frame getFrame(int offset) {
393 } else {
394 //split
395 Label newStart = labelContext.newLabel();
396 labelContext.setLabelTarget(newStart, rangeEnd);
397 Label newEnd = labelContext.newLabel();
398 labelContext.setLabelTarget(newEnd, rangeStart);
399 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
400 it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
401 }
402 }
403 }
404
405 /**
406 * Getter of the generated <code>StackMapTableAttribute</code> or null if stack map is empty
407 * @return <code>StackMapTableAttribute</code> or null if stack map is empty
408 */
409 public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
410 return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
411 @Override
412 public void writeBody(BufWriterImpl b) {
413 if (framesCount != (char) framesCount) {
414 throw generatorError("Too many frames: " + framesCount);
415 }
416 b.writeU2(framesCount);
417 Frame prevFrame = new Frame(classHierarchy);
418 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
419 prevFrame.trimAndCompress();
420 for (int i = 0; i < framesCount; i++) {
421 var fr = frames[i];
422 fr.trimAndCompress();
423 fr.writeTo(b, prevFrame, cp);
424 prevFrame = fr;
425 }
426 }
427
428 @Override
429 public Utf8Entry attributeName() {
430 return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
431 }
432 };
433 }
434
435 private static Type cpIndexToType(int index, ConstantPoolBuilder cp) {
436 return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol());
437 }
438
439 private void processMethod() {
440 var frames = this.frames;
441 var currentFrame = this.currentFrame;
442 currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
443 currentFrame.stackSize = 0;
444 currentFrame.offset = -1;
445 int stackmapIndex = 0;
446 var bcs = bytecode.start();
447 boolean ncf = false;
448 while (bcs.next()) {
449 currentFrame.offset = bcs.bci();
450 if (stackmapIndex < framesCount) {
451 int thisOffset = frames[stackmapIndex].offset;
452 if (ncf && thisOffset > bcs.bci()) {
453 throw generatorError("Expecting a stack map frame");
454 }
455 if (thisOffset == bcs.bci()) {
456 Frame nextFrame = frames[stackmapIndex++];
457 if (!ncf) {
458 currentFrame.checkAssignableTo(nextFrame);
459 }
460 while (!nextFrame.dirty) { //skip unmatched frames
461 if (stackmapIndex == framesCount) return; //skip the rest of this round
462 nextFrame = frames[stackmapIndex++];
463 }
466 currentFrame.offset = bcs.bci();
467 currentFrame.copyFrom(nextFrame);
468 nextFrame.dirty = false;
469 } else if (thisOffset < bcs.bci()) {
470 throw generatorError("Bad stack map offset");
471 }
472 } else if (ncf) {
473 throw generatorError("Expecting a stack map frame");
474 }
475 ncf = processBlock(bcs);
476 }
477 }
478
479 private boolean processBlock(RawBytecodeHelper bcs) {
480 int opcode = bcs.opcode();
481 boolean ncf = false;
482 boolean this_uninit = false;
483 boolean verified_exc_handlers = false;
484 int bci = bcs.bci();
485 Type type1, type2, type3, type4;
486 if ((RawBytecodeHelper.isStoreIntoLocal(opcode) || (opcode == PUTFIELD && OBJECT_INITIALIZER_NAME.equals(methodName)))
487 && bci >= exMin && bci < exMax) {
488 processExceptionHandlerTargets(bci, this_uninit);
489 verified_exc_handlers = true;
490 }
491 switch (opcode) {
492 case NOP -> {}
493 case RETURN -> {
494 ncf = true;
495 }
496 case ACONST_NULL ->
497 currentFrame.pushStack(Type.NULL_TYPE);
498 case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH ->
499 currentFrame.pushStack(Type.INTEGER_TYPE);
500 case LCONST_0, LCONST_1 ->
501 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
502 case FCONST_0, FCONST_1, FCONST_2 ->
503 currentFrame.pushStack(Type.FLOAT_TYPE);
504 case DCONST_0, DCONST_1 ->
505 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
506 case LDC ->
507 processLdc(bcs.getIndexU1());
690 type1 = cpIndexToType(bcs.getIndexU2(), cp);
691 int dim = bcs.getU1Unchecked(bcs.bci() + 3);
692 for (int i = 0; i < dim; i++) {
693 currentFrame.popStack();
694 }
695 currentFrame.pushStack(type1);
696 }
697 case JSR, JSR_W, RET ->
698 throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0");
699 default ->
700 throw generatorError(String.format("Bad instruction: %02x", opcode));
701 }
702 if (!verified_exc_handlers && bci >= exMin && bci < exMax) {
703 processExceptionHandlerTargets(bci, this_uninit);
704 }
705 return ncf;
706 }
707
708 private void processExceptionHandlerTargets(int bci, boolean this_uninit) {
709 for (var ex : rawHandlers) {
710 if (bci == ex.start || (currentFrame.localsOrUnsetsChanged && bci > ex.start && bci < ex.end)) {
711 int flags = currentFrame.flags;
712 if (this_uninit) flags |= FLAG_THIS_UNINIT;
713 Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType);
714 checkJumpTarget(newFrame, ex.handler);
715 }
716 }
717 currentFrame.localsOrUnsetsChanged = false;
718 }
719
720 private void processLdc(int index) {
721 switch (cp.entryByIndex(index).tag()) {
722 case TAG_UTF8 ->
723 currentFrame.pushStack(Type.OBJECT_TYPE);
724 case TAG_STRING ->
725 currentFrame.pushStack(Type.STRING_TYPE);
726 case TAG_CLASS ->
727 currentFrame.pushStack(Type.CLASS_TYPE);
728 case TAG_INTEGER ->
729 currentFrame.pushStack(Type.INTEGER_TYPE);
730 case TAG_FLOAT ->
731 currentFrame.pushStack(Type.FLOAT_TYPE);
732 case TAG_DOUBLE ->
733 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
734 case TAG_LONG ->
735 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
736 case TAG_METHOD_HANDLE ->
737 currentFrame.pushStack(Type.METHOD_HANDLE_TYPE);
767 throw generatorError("number of keys in lookupswitch less than 0");
768 }
769 delta = 2;
770 for (int i = 0; i < (keys - 1); i++) {
771 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
772 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
773 if (this_key >= next_key) {
774 throw generatorError("Bad lookupswitch instruction");
775 }
776 }
777 }
778 int target = bci + defaultOffset;
779 checkJumpTarget(currentFrame, target);
780 for (int i = 0; i < keys; i++) {
781 target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
782 checkJumpTarget(currentFrame, target);
783 }
784 }
785
786 private void processFieldInstructions(RawBytecodeHelper bcs) {
787 var nameAndType = cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType();
788 var desc = Util.fieldTypeSymbol(nameAndType.type());
789 var currentFrame = this.currentFrame;
790 switch (bcs.opcode()) {
791 case GETSTATIC ->
792 currentFrame.pushStack(desc);
793 case PUTSTATIC -> {
794 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
795 }
796 case GETFIELD -> {
797 currentFrame.decStack(1);
798 currentFrame.pushStack(desc);
799 }
800 case PUTFIELD -> {
801 if (strictFieldsToPut.length > 0) {
802 currentFrame.putStrictField(nameAndType);
803 }
804 currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
805 }
806 default -> throw new AssertionError("Should not reach here");
807 }
808 }
809
810 private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) {
811 int index = bcs.getIndexU2();
812 int opcode = bcs.opcode();
813 var nameAndType = opcode == INVOKEDYNAMIC
814 ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType()
815 : cp.entryByIndex(index, MemberRefEntry.class).nameAndType();
816 var mDesc = Util.methodTypeSymbol(nameAndType.type());
817 int bci = bcs.bci();
818 var currentFrame = this.currentFrame;
819 currentFrame.decStack(Util.parameterSlots(mDesc));
820 if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) {
821 if (nameAndType.name().equalsString(OBJECT_INITIALIZER_NAME)) {
822 Type type = currentFrame.popStack();
823 if (type == Type.UNITIALIZED_THIS_TYPE) {
824 if (inTryBlock) {
825 processExceptionHandlerTargets(bci, true);
826 }
827 var owner = cp.entryByIndex(index, MemberRefEntry.class).owner();
828 if (!owner.name().equalsString(((ClassOrInterfaceDescImpl) thisType.sym).internalName())
829 && currentFrame.unsetFieldsSize != 0) {
830 throw generatorError("Unset fields mismatch");
831 }
832 currentFrame.initializeObject(type, thisType);
833 currentFrame.unsetFieldsSize = 0;
834 currentFrame.unsetFields = UnsetField.EMPTY_ARRAY;
835 thisUninit = true;
836 } else if (type.tag == ITEM_UNINITIALIZED) {
837 Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
838 if (inTryBlock) {
839 processExceptionHandlerTargets(bci, thisUninit);
840 }
841 currentFrame.initializeObject(type, new_class_type);
842 } else {
843 throw generatorError("Bad operand type when invoking <init>");
844 }
845 } else {
846 currentFrame.decStack(1);
847 }
848 }
849 currentFrame.pushStack(mDesc.returnType());
850 return thisUninit;
851 }
852
853 private Type getNewarrayType(int index) {
854 if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));
956 return;
957 }
958 if (frameOffset > offset) {
959 break;
960 }
961 }
962 if (framesCount >= frames.length) {
963 int newCapacity = framesCount + 8;
964 this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
965 }
966 if (i != framesCount) {
967 System.arraycopy(frames, i, frames, i + 1, framesCount - i);
968 }
969 frames[i] = new Frame(offset, classHierarchy);
970 this.framesCount = framesCount + 1;
971 }
972
973 private final class Frame {
974
975 int offset;
976 int localsSize, stackSize, unsetFieldsSize;
977 int flags;
978 int frameMaxStack = 0, frameMaxLocals = 0;
979 boolean dirty = false;
980 boolean localsOrUnsetsChanged = false;
981
982 private final ClassHierarchyImpl classHierarchy;
983
984 private Type[] locals, stack;
985 private UnsetField[] unsetFields; // sorted, modifiable oversized array
986
987 Frame(ClassHierarchyImpl classHierarchy) {
988 this(-1, 0, 0, 0, 0, null, null, UnsetField.EMPTY_ARRAY, classHierarchy);
989 }
990
991 Frame(int offset, ClassHierarchyImpl classHierarchy) {
992 this(offset, -1, 0, 0, 0, null, null, UnsetField.EMPTY_ARRAY, classHierarchy);
993 }
994
995 Frame(int offset, int flags, int locals_size, int stack_size, int unsetFieldsSize, Type[] locals, Type[] stack, UnsetField[] unsetFields, ClassHierarchyImpl classHierarchy) {
996 this.offset = offset;
997 this.localsSize = locals_size;
998 this.stackSize = stack_size;
999 this.unsetFieldsSize = unsetFieldsSize;
1000 this.flags = flags;
1001 this.locals = locals;
1002 this.stack = stack;
1003 this.unsetFields = unsetFields;
1004 this.classHierarchy = classHierarchy;
1005 }
1006
1007 @Override
1008 public String toString() {
1009 return (dirty ? "frame* @" : "frame @") + offset +
1010 " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) +
1011 " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)) +
1012 " and unset fields " + (unsetFields == null ? "[]" : Arrays.asList(unsetFields).subList(0, unsetFieldsSize));
1013 }
1014
1015 Frame pushStack(ClassDesc desc) {
1016 if (desc == CD_long) return pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
1017 if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
1018 return desc == CD_void ? this
1019 : pushStack(
1020 desc.isPrimitive()
1021 ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
1022 : Type.referenceType(desc));
1023 }
1024
1025 Frame pushStack(Type type) {
1026 checkStack(stackSize);
1027 stack[stackSize++] = type;
1028 return this;
1029 }
1030
1031 Frame pushStack(Type type1, Type type2) {
1032 checkStack(stackSize + 1);
1033 stack[stackSize++] = type1;
1034 stack[stackSize++] = type2;
1035 return this;
1036 }
1037
1038 Type popStack() {
1039 if (stackSize < 1) throw generatorError("Operand stack underflow");
1040 return stack[--stackSize];
1041 }
1042
1043 Frame decStack(int size) {
1044 stackSize -= size;
1045 if (stackSize < 0) throw generatorError("Operand stack underflow");
1046 return this;
1047 }
1048
1049 Frame frameInExceptionHandler(int flags, Type excType) {
1050 return new Frame(offset, flags, localsSize, 1, unsetFieldsSize,
1051 locals, new Type[] {excType}, unsetFields, classHierarchy);
1052 }
1053
1054 void initializeObject(Type old_object, Type new_object) {
1055 int i;
1056 for (i = 0; i < localsSize; i++) {
1057 if (locals[i].equals(old_object)) {
1058 locals[i] = new_object;
1059 localsOrUnsetsChanged = true;
1060 }
1061 }
1062 for (i = 0; i < stackSize; i++) {
1063 if (stack[i].equals(old_object)) {
1064 stack[i] = new_object;
1065 }
1066 }
1067 if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1068 flags &= ~FLAG_THIS_UNINIT;
1069 assert flags == 0 : flags;
1070 }
1071 }
1072
1073 Frame checkLocal(int index) {
1074 if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1075 if (locals == null) {
1076 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1077 Arrays.fill(locals, Type.TOP_TYPE);
1078 } else if (index >= locals.length) {
1079 int current = locals.length;
1080 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1081 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1082 }
1083 return this;
1084 }
1085
1086 void putStrictField(NameAndTypeEntry nat) {
1087 int shift = 0;
1088 var array = unsetFields;
1089 for (int i = 0; i < unsetFieldsSize; i++) {
1090 var f = array[i];
1091 if (f.name().equals(nat.name()) && f.type().equals(nat.type())) {
1092 shift++;
1093 } else if (shift != 0) {
1094 array[i - shift] = array[i];
1095 array[i] = null;
1096 }
1097 }
1098 if (shift > 1) {
1099 throw generatorError(nat + "; discovered " + shift);
1100 } else if (shift == 1) {
1101 localsOrUnsetsChanged = true;
1102 }
1103 unsetFieldsSize -= shift;
1104 }
1105
1106 private void checkStack(int index) {
1107 if (index >= frameMaxStack) frameMaxStack = index + 1;
1108 if (stack == null) {
1109 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1110 Arrays.fill(stack, Type.TOP_TYPE);
1111 } else if (index >= stack.length) {
1112 int current = stack.length;
1113 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1114 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1115 }
1116 }
1117
1118 private void setLocalRawInternal(int index, Type type) {
1119 checkLocal(index);
1120 localsOrUnsetsChanged |= !type.equals(locals[index]);
1121 locals[index] = type;
1122 }
1123
1124 void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass, UnsetField[] strictFieldsToPut) {
1125 int localsSize = 0;
1126 // Pre-emptively create a locals array that encompass all parameter slots
1127 checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1128 Type type;
1129 Type[] locals = this.locals;
1130 if (!isStatic) {
1131 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
1132 int strictFieldCount = strictFieldsToPut.length;
1133 this.unsetFields = UnsetField.copyArray(strictFieldsToPut, strictFieldCount);
1134 this.unsetFieldsSize = strictFieldCount;
1135 type = Type.UNITIALIZED_THIS_TYPE;
1136 this.flags = FLAG_THIS_UNINIT;
1137 } else {
1138 this.unsetFields = UnsetField.EMPTY_ARRAY;
1139 this.unsetFieldsSize = 0;
1140 type = thisKlass;
1141 this.flags = 0;
1142 }
1143 locals[localsSize++] = type;
1144 }
1145 for (int i = 0; i < methodDesc.parameterCount(); i++) {
1146 var desc = methodDesc.parameterType(i);
1147 if (desc == CD_long) {
1148 locals[localsSize ] = Type.LONG_TYPE;
1149 locals[localsSize + 1] = Type.LONG2_TYPE;
1150 localsSize += 2;
1151 } else if (desc == CD_double) {
1152 locals[localsSize ] = Type.DOUBLE_TYPE;
1153 locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1154 localsSize += 2;
1155 } else {
1156 if (!desc.isPrimitive()) {
1157 type = Type.referenceType(desc);
1158 } else if (desc == CD_float) {
1159 type = Type.FLOAT_TYPE;
1160 } else {
1161 type = Type.INTEGER_TYPE;
1162 }
1163 locals[localsSize++] = type;
1164 }
1165 }
1166 if (locals != null && localsSize < locals.length) {
1167 Arrays.fill(locals, localsSize, locals.length, Type.TOP_TYPE);
1168 }
1169 this.localsSize = localsSize;
1170 }
1171
1172 void copyFrom(Frame src) {
1173 if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1174 localsSize = src.localsSize;
1175 checkLocal(src.localsSize - 1);
1176 if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1177 if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1178 stackSize = src.stackSize;
1179 checkStack(src.stackSize - 1);
1180 if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);
1181 unsetFieldsSize = src.unsetFieldsSize;
1182 unsetFields = UnsetField.copyArray(src.unsetFields, src.unsetFieldsSize);
1183 flags = src.flags;
1184 localsOrUnsetsChanged = true;
1185 }
1186
1187 void checkAssignableTo(Frame target) {
1188 int localsSize = this.localsSize;
1189 int stackSize = this.stackSize;
1190 int myUnsetFieldsSize = this.unsetFieldsSize;
1191 if (target.flags == -1) {
1192 target.locals = locals == null ? null : locals.clone();
1193 target.localsSize = localsSize;
1194 if (stackSize > 0) {
1195 target.stack = stack.clone();
1196 target.stackSize = stackSize;
1197 }
1198 target.unsetFields = UnsetField.copyArray(this.unsetFields, myUnsetFieldsSize);
1199 target.unsetFieldsSize = myUnsetFieldsSize;
1200 target.flags = flags;
1201 target.dirty = true;
1202 } else {
1203 if (target.localsSize > localsSize) {
1204 target.localsSize = localsSize;
1205 target.dirty = true;
1206 }
1207 for (int i = 0; i < target.localsSize; i++) {
1208 merge(locals[i], target.locals, i, target);
1209 }
1210 if (stackSize != target.stackSize) {
1211 throw generatorError("Stack size mismatch");
1212 }
1213 for (int i = 0; i < target.stackSize; i++) {
1214 if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1215 throw generatorError("Stack content mismatch");
1216 }
1217 }
1218 if (myUnsetFieldsSize != 0) {
1219 mergeUnsetFields(target);
1220 }
1221 }
1222 }
1223
1224 private Type getLocalRawInternal(int index) {
1225 checkLocal(index);
1226 return locals[index];
1227 }
1228
1229 Type getLocal(int index) {
1230 Type ret = getLocalRawInternal(index);
1231 if (index >= localsSize) {
1232 localsSize = index + 1;
1233 }
1234 return ret;
1235 }
1236
1237 void setLocal(int index, Type type) {
1238 Type old = getLocalRawInternal(index);
1239 if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1240 setLocalRawInternal(index + 1, Type.TOP_TYPE);
1257 if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1258 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1259 }
1260 setLocalRawInternal(index, type1);
1261 setLocalRawInternal(index + 1, type2);
1262 if (index >= localsSize - 1) {
1263 localsSize = index + 2;
1264 }
1265 }
1266
1267 private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1268 var to = toTypes[i];
1269 var newTo = to.mergeFrom(me, classHierarchy);
1270 if (to != newTo && !to.equals(newTo)) {
1271 toTypes[i] = newTo;
1272 target.dirty = true;
1273 }
1274 return newTo;
1275 }
1276
1277 // Merge this frame's unset fields into the target frame
1278 private void mergeUnsetFields(Frame target) {
1279 int myUnsetSize = unsetFieldsSize;
1280 int targetUnsetSize = target.unsetFieldsSize;
1281 var myUnsets = unsetFields;
1282 var targetUnsets = target.unsetFields;
1283 if (UnsetField.matches(myUnsets, myUnsetSize, targetUnsets, targetUnsetSize)) {
1284 return; // no merge
1285 }
1286 // merge sort
1287 var merged = new UnsetField[StackMapGenerator.this.strictFieldsToPut.length];
1288 int mergedSize = 0;
1289 int i = 0;
1290 int j = 0;
1291 while (i < myUnsetSize && j < targetUnsetSize) {
1292 var myCandidate = myUnsets[i];
1293 var targetCandidate = targetUnsets[j];
1294 var cmp = myCandidate.compareTo(targetCandidate);
1295 if (cmp == 0) {
1296 merged[mergedSize++] = myCandidate;
1297 i++;
1298 j++;
1299 } else if (cmp < 0) {
1300 merged[mergedSize++] = myCandidate;
1301 i++;
1302 } else {
1303 merged[mergedSize++] = targetCandidate;
1304 j++;
1305 }
1306 }
1307 if (i < myUnsetSize) {
1308 int len = myUnsetSize - i;
1309 System.arraycopy(myUnsets, i, merged, mergedSize, len);
1310 mergedSize += len;
1311 } else if (j < targetUnsetSize) {
1312 int len = targetUnsetSize - j;
1313 System.arraycopy(targetUnsets, j, merged, mergedSize, len);
1314 mergedSize += len;
1315 }
1316
1317 target.unsetFieldsSize = mergedSize;
1318 target.unsetFields = merged;
1319 target.dirty = true;
1320 }
1321
1322 private static int trimAndCompress(Type[] types, int count) {
1323 while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1324 int compressed = 0;
1325 for (int i = 0; i < count; i++) {
1326 if (!types[i].isCategory2_2nd()) {
1327 if (compressed != i) {
1328 types[compressed] = types[i];
1329 }
1330 compressed++;
1331 }
1332 }
1333 return compressed;
1334 }
1335
1336 void trimAndCompress() {
1337 localsSize = trimAndCompress(locals, localsSize);
1338 stackSize = trimAndCompress(stack, stackSize);
1339 }
1340
1341 boolean hasUninitializedThis() {
1342 int size = this.localsSize;
1343 var localVars = this.locals;
1344 for (int i = 0; i < size; i++) {
1345 if (localVars[i] == Type.UNITIALIZED_THIS_TYPE)
1346 return true;
1347 }
1348 return false;
1349 }
1350
1351 private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1352 if (l1 == null || l2 == null) return commonSize == 0;
1353 return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1354 }
1355
1356 // In sync with StackMapDecoder::needsLarvalFrameForTransition
1357 private boolean needsLarvalFrame(Frame prevFrame) {
1358 if (UnsetField.matches(unsetFields, unsetFieldsSize, prevFrame.unsetFields, prevFrame.unsetFieldsSize))
1359 return false;
1360 if (!hasUninitializedThis()) {
1361 assert unsetFieldsSize == 0 : this; // Should have been handled by processInvokeInstructions
1362 return false;
1363 }
1364 return true;
1365 }
1366
1367 void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
1368 // enclosing frames
1369 if (needsLarvalFrame(prevFrame)) {
1370 out.writeU1U2(EARLY_LARVAL, unsetFieldsSize);
1371 for (int i = 0; i < unsetFieldsSize; i++) {
1372 var f = unsetFields[i];
1373 out.writeIndex(cp.nameAndTypeEntry(f.name(), f.type()));
1374 }
1375 }
1376 // base frame
1377 int localsSize = this.localsSize;
1378 int stackSize = this.stackSize;
1379 int offsetDelta = offset - prevFrame.offset - 1;
1380 if (stackSize == 0) {
1381 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1382 int diffLocalsSize = localsSize - prevFrame.localsSize;
1383 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1384 if (diffLocalsSize == 0 && offsetDelta <= SAME_FRAME_END) { //same frame
1385 out.writeU1(offsetDelta);
1386 } else { //chop, same extended or append frame
1387 out.writeU1U2(SAME_FRAME_EXTENDED + diffLocalsSize, offsetDelta);
1388 for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1389 }
1390 return;
1391 }
1392 } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1393 if (offsetDelta <= SAME_LOCALS_1_STACK_ITEM_FRAME_END - SAME_LOCALS_1_STACK_ITEM_FRAME_START) { //same locals 1 stack item frame
1394 out.writeU1(SAME_LOCALS_1_STACK_ITEM_FRAME_START + offsetDelta);
1395 } else { //same locals 1 stack item extended frame
1396 out.writeU1U2(SAME_LOCALS_1_STACK_ITEM_EXTENDED, offsetDelta);
|