< prev index next >

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

Print this page
*** 1,7 ***
  /*
!  * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
   * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
--- 1,7 ---
  /*
!  * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
   * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as

*** 28,24 ***
  
  import java.lang.classfile.Attribute;
  import java.lang.classfile.Attributes;
  import java.lang.classfile.Label;
  import java.lang.classfile.attribute.StackMapTableAttribute;
! import java.lang.classfile.constantpool.ClassEntry;
- import java.lang.classfile.constantpool.ConstantDynamicEntry;
- import java.lang.classfile.constantpool.ConstantPoolBuilder;
- import java.lang.classfile.constantpool.InvokeDynamicEntry;
- import java.lang.classfile.constantpool.MemberRefEntry;
- import java.lang.classfile.constantpool.Utf8Entry;
  import java.lang.constant.ClassDesc;
  import java.lang.constant.MethodTypeDesc;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Objects;
  import java.util.stream.Collectors;
  
  import jdk.internal.constant.ClassOrInterfaceDescImpl;
  import jdk.internal.util.Preconditions;
  
  import static java.lang.classfile.ClassFile.*;
  import static java.lang.classfile.constantpool.PoolEntry.*;
--- 28,20 ---
  
  import java.lang.classfile.Attribute;
  import java.lang.classfile.Attributes;
  import java.lang.classfile.Label;
  import java.lang.classfile.attribute.StackMapTableAttribute;
! import java.lang.classfile.constantpool.*;
  import java.lang.constant.ClassDesc;
  import java.lang.constant.MethodTypeDesc;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Objects;
  import java.util.stream.Collectors;
  
+ import jdk.internal.classfile.impl.WritableField.UnsetField;
  import jdk.internal.constant.ClassOrInterfaceDescImpl;
  import jdk.internal.util.Preconditions;
  
  import static java.lang.classfile.ClassFile.*;
  import static java.lang.classfile.constantpool.PoolEntry.*;

*** 155,10 ***
--- 151,11 ---
                  dcb.methodInfo.methodTypeSymbol(),
                  (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0,
                  dcb.bytecodesBufWriter.bytecodeView(),
                  dcb.constantPool,
                  dcb.context,
+                 buf.getStrictInstanceFields(),
                  dcb.handlers);
      }
  
      private static final String OBJECT_INITIALIZER_NAME = "<init>";
      private static final int FLAG_THIS_UNINIT = 0x01;

*** 196,10 ***
--- 193,11 ---
      private final boolean isStatic;
      private final LabelContext labelContext;
      private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
      private final List<RawExceptionCatch> rawHandlers;
      private final ClassHierarchyImpl classHierarchy;
+     private final UnsetField[] strictFieldsToPut; // exact-sized, do not modify this copy!
      private final boolean patchDeadCode;
      private final boolean filterDeadLabels;
      private Frame[] frames = EMPTY_FRAME_ARRAY;
      private int framesCount = 0;
      private final Frame currentFrame;

*** 227,10 ***
--- 225,11 ---
                       MethodTypeDesc methodDesc,
                       boolean isStatic,
                       RawBytecodeHelper.CodeRange bytecode,
                       SplitConstantPool cp,
                       ClassFileImpl context,
+                      UnsetField[] strictFields,
                       List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
          this.thisType = Type.referenceType(thisClass);
          this.methodName = methodName;
          this.methodDesc = methodDesc;
          this.isStatic = isStatic;

*** 241,10 ***
--- 240,15 ---
          this.rawHandlers = new ArrayList<>(handlers.size());
          this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
          this.patchDeadCode = context.patchDeadCode();
          this.filterDeadLabels = context.dropDeadLabels();
          this.currentFrame = new Frame(classHierarchy);
+         if (OBJECT_INITIALIZER_NAME.equals(methodName)) {
+             this.strictFieldsToPut = strictFields;
+         } else {
+             this.strictFieldsToPut = UnsetField.EMPTY_ARRAY;
+         }
          generate();
      }
  
      /**
       * Calculated maximum number of the locals required

*** 389,20 ***
       */
      public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
          return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
              @Override
              public void writeBody(BufWriterImpl b) {
                  b.writeU2(framesCount);
                  Frame prevFrame =  new Frame(classHierarchy);
!                 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
                  prevFrame.trimAndCompress();
                  for (int i = 0; i < framesCount; i++) {
                      var fr = frames[i];
                      fr.trimAndCompress();
!                     fr.writeTo(b, prevFrame, cp);
                      prevFrame = fr;
                  }
              }
  
              @Override
              public Utf8Entry attributeName() {
                  return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
--- 393,29 ---
       */
      public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
          return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
              @Override
              public void writeBody(BufWriterImpl b) {
+                 int countPos = b.size();
                  b.writeU2(framesCount);
+                 int extraFrameCount = 0;
                  Frame prevFrame =  new Frame(classHierarchy);
!                 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
                  prevFrame.trimAndCompress();
                  for (int i = 0; i < framesCount; i++) {
                      var fr = frames[i];
                      fr.trimAndCompress();
!                     extraFrameCount += fr.writeTo(b, prevFrame, cp);
                      prevFrame = fr;
                  }
+                 if (extraFrameCount > 0) {
+                     int size = framesCount + extraFrameCount;
+                     if (size != (char) size) {
+                         throw generatorError("Too many frames: " + size);
+                     }
+                     b.patchU2(countPos, size);
+                 }
              }
  
              @Override
              public Utf8Entry attributeName() {
                  return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);

*** 415,13 ***
      }
  
      private void processMethod() {
          var frames = this.frames;
          var currentFrame = this.currentFrame;
!         currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
          currentFrame.stackSize = 0;
-         currentFrame.flags = 0;
          currentFrame.offset = -1;
          int stackmapIndex = 0;
          var bcs = bytecode.start();
          boolean ncf = false;
          while (bcs.next()) {
--- 428,12 ---
      }
  
      private void processMethod() {
          var frames = this.frames;
          var currentFrame = this.currentFrame;
!         currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
          currentFrame.stackSize = 0;
          currentFrame.offset = -1;
          int stackmapIndex = 0;
          var bcs = bytecode.start();
          boolean ncf = false;
          while (bcs.next()) {

*** 760,11 ***
              checkJumpTarget(currentFrame, target);
          }
      }
  
      private void processFieldInstructions(RawBytecodeHelper bcs) {
!         var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).type());
          var currentFrame = this.currentFrame;
          switch (bcs.opcode()) {
              case GETSTATIC ->
                  currentFrame.pushStack(desc);
              case PUTSTATIC -> {
--- 772,12 ---
              checkJumpTarget(currentFrame, target);
          }
      }
  
      private void processFieldInstructions(RawBytecodeHelper bcs) {
!         var nameAndType = cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType();
+         var desc = Util.fieldTypeSymbol(nameAndType.type());
          var currentFrame = this.currentFrame;
          switch (bcs.opcode()) {
              case GETSTATIC ->
                  currentFrame.pushStack(desc);
              case PUTSTATIC -> {

*** 773,10 ***
--- 786,13 ---
              case GETFIELD -> {
                  currentFrame.decStack(1);
                  currentFrame.pushStack(desc);
              }
              case PUTFIELD -> {
+                 if (strictFieldsToPut.length > 0) {
+                     currentFrame.putStrictField(nameAndType);
+                 }
                  currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
              }
              default -> throw new AssertionError("Should not reach here");
          }
      }

*** 796,11 ***
--- 812,18 ---
                  Type type = currentFrame.popStack();
                  if (type == Type.UNITIALIZED_THIS_TYPE) {
                      if (inTryBlock) {
                          processExceptionHandlerTargets(bci, true);
                      }
+                     var owner = cp.entryByIndex(index, MemberRefEntry.class).owner();
+                     if (!owner.name().equalsString(((ClassOrInterfaceDescImpl) thisType.sym).internalName())
+                             && currentFrame.unsetFieldsSize != 0) {
+                         throw generatorError("Unset fields mismatch");
+                     }
                      currentFrame.initializeObject(type, thisType);
+                     currentFrame.unsetFieldsSize = 0;
+                     currentFrame.unsetFields = UnsetField.EMPTY_ARRAY;
                      thisUninit = true;
                  } else if (type.tag == ITEM_UNINITIALIZED) {
                      Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
                      if (inTryBlock) {
                          processExceptionHandlerTargets(bci, thisUninit);

*** 939,19 ***
      }
  
      private final class Frame {
  
          int offset;
!         int localsSize, stackSize;
          int flags;
          int frameMaxStack = 0, frameMaxLocals = 0;
          boolean dirty = false;
          boolean localsChanged = false;
  
          private final ClassHierarchyImpl classHierarchy;
  
          private Type[] locals, stack;
  
          Frame(ClassHierarchyImpl classHierarchy) {
              this(-1, 0, 0, 0, null, null, classHierarchy);
          }
  
--- 962,20 ---
      }
  
      private final class Frame {
  
          int offset;
!         int localsSize, stackSize, unsetFieldsSize;
          int flags;
          int frameMaxStack = 0, frameMaxLocals = 0;
          boolean dirty = false;
          boolean localsChanged = false;
  
          private final ClassHierarchyImpl classHierarchy;
  
          private Type[] locals, stack;
+         private UnsetField[] unsetFields = UnsetField.EMPTY_ARRAY; // sorted, modifiable oversized array
  
          Frame(ClassHierarchyImpl classHierarchy) {
              this(-1, 0, 0, 0, null, null, classHierarchy);
          }
  

*** 1024,11 ***
                  if (stack[i].equals(old_object)) {
                      stack[i] = new_object;
                  }
              }
              if (old_object == Type.UNITIALIZED_THIS_TYPE) {
!                 flags = 0;
              }
          }
  
          Frame checkLocal(int index) {
              if (index >= frameMaxLocals) frameMaxLocals = index + 1;
--- 1048,12 ---
                  if (stack[i].equals(old_object)) {
                      stack[i] = new_object;
                  }
              }
              if (old_object == Type.UNITIALIZED_THIS_TYPE) {
!                 flags &= ~FLAG_THIS_UNINIT;
+                 assert flags == 0 : flags;
              }
          }
  
          Frame checkLocal(int index) {
              if (index >= frameMaxLocals) frameMaxLocals = index + 1;

*** 1041,10 ***
--- 1066,28 ---
                  Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
              }
              return this;
          }
  
+         void putStrictField(NameAndTypeEntry nat) {
+             int shift = 0;
+             var array = unsetFields;
+             for (int i = 0; i < unsetFieldsSize; i++) {
+                 var f = array[i];
+                 if (f.name().equals(nat.name()) && f.type().equals(nat.type())) {
+                     shift++;
+                 } else if (shift != 0) {
+                     array[i - shift] = array[i];
+                     array[i] = null;
+                 }
+             }
+             if (shift > 1) {
+                 throw generatorError(nat + "; discovered " + shift);
+             }
+             unsetFieldsSize -= shift;
+         }
+ 
          private void checkStack(int index) {
              if (index >= frameMaxStack) frameMaxStack = index + 1;
              if (stack == null) {
                  stack = new Type[index + FRAME_DEFAULT_CAPACITY];
                  Arrays.fill(stack, Type.TOP_TYPE);

*** 1059,22 ***
              checkLocal(index);
              localsChanged |= !type.equals(locals[index]);
              locals[index] = type;
          }
  
!         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) {
              int localsSize = 0;
              // Pre-emptively create a locals array that encompass all parameter slots
              checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
              Type type;
              Type[] locals = this.locals;
              if (!isStatic) {
                  if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
                      type = Type.UNITIALIZED_THIS_TYPE;
!                     flags |= FLAG_THIS_UNINIT;
                  } else {
                      type = thisKlass;
                  }
                  locals[localsSize++] = type;
              }
              for (int i = 0; i < methodDesc.parameterCount(); i++) {
                  var desc = methodDesc.parameterType(i);
--- 1102,28 ---
              checkLocal(index);
              localsChanged |= !type.equals(locals[index]);
              locals[index] = type;
          }
  
!         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass, UnsetField[] strictFieldsToPut) {
              int localsSize = 0;
              // Pre-emptively create a locals array that encompass all parameter slots
              checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
              Type type;
              Type[] locals = this.locals;
              if (!isStatic) {
                  if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
+                     int strictFieldCount = strictFieldsToPut.length;
+                     this.unsetFields = UnsetField.copyArray(strictFieldsToPut, strictFieldCount);
+                     this.unsetFieldsSize = strictFieldCount;
                      type = Type.UNITIALIZED_THIS_TYPE;
!                     this.flags = FLAG_THIS_UNINIT;
                  } else {
+                     this.unsetFields = UnsetField.EMPTY_ARRAY;
+                     this.unsetFieldsSize = 0;
                      type = thisKlass;
+                     this.flags = 0;
                  }
                  locals[localsSize++] = type;
              }
              for (int i = 0; i < methodDesc.parameterCount(); i++) {
                  var desc = methodDesc.parameterType(i);

*** 1107,24 ***
--- 1156,29 ---
              if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
              if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
              stackSize = src.stackSize;
              checkStack(src.stackSize - 1);
              if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);
+             unsetFieldsSize = src.unsetFieldsSize;
+             unsetFields = UnsetField.copyArray(src.unsetFields, src.unsetFieldsSize);
              flags = src.flags;
              localsChanged = true;
          }
  
          void checkAssignableTo(Frame target) {
              int localsSize = this.localsSize;
              int stackSize = this.stackSize;
+             int myUnsetFieldsSize = this.unsetFieldsSize;
              if (target.flags == -1) {
                  target.locals = locals == null ? null : locals.clone();
                  target.localsSize = localsSize;
                  if (stackSize > 0) {
                      target.stack = stack.clone();
                      target.stackSize = stackSize;
                  }
+                 target.unsetFields = UnsetField.copyArray(this.unsetFields, myUnsetFieldsSize);
+                 target.unsetFieldsSize = myUnsetFieldsSize;
                  target.flags = flags;
                  target.dirty = true;
              } else {
                  if (target.localsSize > localsSize) {
                      target.localsSize = localsSize;

*** 1139,10 ***
--- 1193,13 ---
                  for (int i = 0; i < target.stackSize; i++) {
                      if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
                          throw generatorError("Stack content mismatch");
                      }
                  }
+                 if (myUnsetFieldsSize != 0) {
+                     mergeUnsetFields(target);
+                 }
              }
          }
  
          private Type getLocalRawInternal(int index) {
              checkLocal(index);

*** 1195,10 ***
--- 1252,55 ---
                  target.dirty = true;
              }
              return newTo;
          }
  
+         // Merge this frame's unset fields into the target frame
+         private void mergeUnsetFields(Frame target) {
+             int myUnsetSize = unsetFieldsSize;
+             int targetUnsetSize = target.unsetFieldsSize;
+             var myUnsets = unsetFields;
+             var targetUnsets = target.unsetFields;
+             if (!UnsetField.mismatches(myUnsets, myUnsetSize, targetUnsets, targetUnsetSize)) {
+                 return; // no merge
+             }
+             // merge sort
+             var merged = new UnsetField[StackMapGenerator.this.strictFieldsToPut.length];
+             int mergedSize = 0;
+             int i = 0;
+             int j = 0;
+             while (i < myUnsetSize && j < targetUnsetSize) {
+                 var myCandidate = myUnsets[i];
+                 var targetCandidate = targetUnsets[j];
+                 var cmp = myCandidate.compareTo(targetCandidate);
+                 if (cmp == 0) {
+                     merged[mergedSize++] = myCandidate;
+                     i++;
+                     j++;
+                 } else if (cmp < 0) {
+                     merged[mergedSize++] = myCandidate;
+                     i++;
+                 } else {
+                     merged[mergedSize++] = targetCandidate;
+                     j++;
+                 }
+             }
+             if (i < myUnsetSize) {
+                 int len = myUnsetSize - i;
+                 System.arraycopy(myUnsets, i, merged, mergedSize, len);
+                 mergedSize += len;
+             } else if (j < targetUnsetSize) {
+                 int len = targetUnsetSize - j;
+                 System.arraycopy(targetUnsets, j, merged, mergedSize, len);
+                 mergedSize += len;
+             }
+ 
+             target.unsetFieldsSize = mergedSize;
+             target.unsetFields = merged;
+             target.dirty = true;
+         }
+ 
          private static int trimAndCompress(Type[] types, int count) {
              while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
              int compressed = 0;
              for (int i = 0; i < count; i++) {
                  if (!types[i].isCategory2_2nd()) {

*** 1219,11 ***
          private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
              if (l1 == null || l2 == null) return commonSize == 0;
              return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
          }
  
!         void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
              int localsSize = this.localsSize;
              int stackSize = this.stackSize;
              int offsetDelta = offset - prevFrame.offset - 1;
              if (stackSize == 0) {
                  int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
--- 1321,22 ---
          private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
              if (l1 == null || l2 == null) return commonSize == 0;
              return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
          }
  
!         int writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
+             int extraFrames = 0;
+             if (UnsetField.mismatches(unsetFields, unsetFieldsSize, prevFrame.unsetFields, prevFrame.unsetFieldsSize)) {
+                 // Emit unset_fields frame
+                 out.writeU1U2(StackMapDecoder.ASSERT_UNSET_FIELDS, unsetFieldsSize);
+                 var array = unsetFields;
+                 for (int i = 0; i < unsetFieldsSize; i++) {
+                     var f = array[i];
+                     out.writeIndex(cp.nameAndTypeEntry(f.name(), f.type()));
+                 }
+                 extraFrames++;
+             }
              int localsSize = this.localsSize;
              int stackSize = this.stackSize;
              int offsetDelta = offset - prevFrame.offset - 1;
              if (stackSize == 0) {
                  int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;

*** 1233,26 ***
                          out.writeU1(offsetDelta);
                      } else {   //chop, same extended or append frame
                          out.writeU1U2(251 + diffLocalsSize, offsetDelta);
                          for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
                      }
!                     return;
                  }
              } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
                  if (offsetDelta < 64) {  //same locals 1 stack item frame
                      out.writeU1(64 + offsetDelta);
                  } else {  //same locals 1 stack item extended frame
                      out.writeU1U2(247, offsetDelta);
                  }
                  stack[0].writeTo(out, cp);
!                 return;
              }
              //full frame
              out.writeU1U2U2(255, offsetDelta, localsSize);
              for (int i=0; i<localsSize; i++) locals[i].writeTo(out, cp);
              out.writeU2(stackSize);
              for (int i=0; i<stackSize; i++) stack[i].writeTo(out, cp);
          }
      }
  
      private static record Type(int tag, ClassDesc sym, int bci) {
  
--- 1346,27 ---
                          out.writeU1(offsetDelta);
                      } else {   //chop, same extended or append frame
                          out.writeU1U2(251 + diffLocalsSize, offsetDelta);
                          for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
                      }
!                     return extraFrames;
                  }
              } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
                  if (offsetDelta < 64) {  //same locals 1 stack item frame
                      out.writeU1(64 + offsetDelta);
                  } else {  //same locals 1 stack item extended frame
                      out.writeU1U2(247, offsetDelta);
                  }
                  stack[0].writeTo(out, cp);
!                 return extraFrames;
              }
              //full frame
              out.writeU1U2U2(255, offsetDelta, localsSize);
              for (int i=0; i<localsSize; i++) locals[i].writeTo(out, cp);
              out.writeU2(stackSize);
              for (int i=0; i<stackSize; i++) stack[i].writeTo(out, cp);
+             return extraFrames;
          }
      }
  
      private static record Type(int tag, ClassDesc sym, int bci) {
  
< prev index next >