< prev index next > src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java
Print this page
/*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+ * 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
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.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.*;
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;
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;
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;
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
*/
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);
+ prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
prevFrame.trimAndCompress();
for (int i = 0; i < framesCount; i++) {
var fr = frames[i];
fr.trimAndCompress();
- fr.writeTo(b, prevFrame, cp);
+ 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);
}
private void processMethod() {
var frames = this.frames;
var currentFrame = this.currentFrame;
- currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
+ currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
currentFrame.stackSize = 0;
- currentFrame.flags = 0;
currentFrame.offset = -1;
int stackmapIndex = 0;
var bcs = bytecode.start();
boolean ncf = false;
while (bcs.next()) {
checkJumpTarget(currentFrame, target);
}
}
private void processFieldInstructions(RawBytecodeHelper bcs) {
- var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).type());
+ 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 -> {
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");
}
}
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);
}
private final class Frame {
int offset;
- int localsSize, stackSize;
+ 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);
}
if (stack[i].equals(old_object)) {
stack[i] = new_object;
}
}
if (old_object == Type.UNITIALIZED_THIS_TYPE) {
- flags = 0;
+ flags &= ~FLAG_THIS_UNINIT;
+ assert flags == 0 : flags;
}
}
Frame checkLocal(int index) {
if (index >= frameMaxLocals) frameMaxLocals = index + 1;
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);
checkLocal(index);
localsChanged |= !type.equals(locals[index]);
locals[index] = type;
}
- void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) {
+ 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;
- flags |= FLAG_THIS_UNINIT;
+ 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);
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;
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);
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()) {
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 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;
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;
+ 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;
+ 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 >