8337225: Demote maxStack and maxLocals from CodeModel to CodeAttribute

Reviewed-by: asotona
This commit is contained in:
Chen Liang 2024-07-29 21:58:08 +00:00
parent bd36b6ae5d
commit ab27090aa0
13 changed files with 166 additions and 88 deletions

View file

@ -43,17 +43,7 @@ import jdk.internal.javac.PreviewFeature;
@PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API) @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
public sealed interface CodeModel public sealed interface CodeModel
extends CompoundElement<CodeElement>, AttributedElement, MethodElement extends CompoundElement<CodeElement>, AttributedElement, MethodElement
permits CodeAttribute, BufferedCodeBuilder.Model, CodeImpl { permits CodeAttribute, BufferedCodeBuilder.Model {
/**
* {@return the maximum size of the local variable table}
*/
int maxLocals();
/**
* {@return the maximum size of the operand stack}
*/
int maxStack();
/** /**
* {@return the enclosing method, if known} * {@return the enclosing method, if known}

View file

@ -47,6 +47,16 @@ import jdk.internal.javac.PreviewFeature;
public sealed interface CodeAttribute extends Attribute<CodeAttribute>, CodeModel public sealed interface CodeAttribute extends Attribute<CodeAttribute>, CodeModel
permits BoundAttribute.BoundCodeAttribute { permits BoundAttribute.BoundCodeAttribute {
/**
* {@return the maximum size of the local variable table}
*/
int maxLocals();
/**
* {@return the maximum size of the operand stack}
*/
int maxStack();
/** /**
* {@return The length of the code array in bytes} * {@return The length of the code array in bytes}
*/ */

View file

@ -35,7 +35,7 @@ import java.lang.classfile.CompoundElement;
public abstract sealed class AbstractUnboundModel<E extends ClassFileElement> public abstract sealed class AbstractUnboundModel<E extends ClassFileElement>
extends AbstractElement extends AbstractElement
implements CompoundElement<E>, AttributedElement, Util.Writable implements CompoundElement<E>, AttributedElement
permits BufferedCodeBuilder.Model, BufferedFieldBuilder.Model, BufferedMethodBuilder.Model { permits BufferedCodeBuilder.Model, BufferedFieldBuilder.Model, BufferedMethodBuilder.Model {
private final List<E> elements; private final List<E> elements;
private List<Attribute<?>> attributes; private List<Attribute<?>> attributes;
@ -63,8 +63,11 @@ public abstract sealed class AbstractUnboundModel<E extends ClassFileElement>
public List<Attribute<?>> attributes() { public List<Attribute<?>> attributes() {
if (attributes == null) if (attributes == null)
attributes = elements.stream() attributes = elements.stream()
.filter(e -> e instanceof Attribute) .<Attribute<?>>mapMulti((e, sink) -> {
.<Attribute<?>>map(e -> (Attribute<?>) e) if (e instanceof Attribute<?> attr) {
sink.accept(attr);
}
})
.toList(); .toList();
return attributes; return attributes;
} }

View file

@ -32,9 +32,6 @@ import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.lang.classfile.Label; import java.lang.classfile.Label;
import java.lang.classfile.MethodModel; import java.lang.classfile.MethodModel;
import java.lang.classfile.instruction.ExceptionCatch; import java.lang.classfile.instruction.ExceptionCatch;
import java.lang.classfile.instruction.IncrementInstruction;
import java.lang.classfile.instruction.LoadInstruction;
import java.lang.classfile.instruction.StoreInstruction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -47,7 +44,6 @@ public final class BufferedCodeBuilder
private final ClassFileImpl context; private final ClassFileImpl context;
private final List<CodeElement> elements = new ArrayList<>(); private final List<CodeElement> elements = new ArrayList<>();
private final LabelImpl startLabel, endLabel; private final LabelImpl startLabel, endLabel;
private final CodeModel original;
private final MethodInfo methodInfo; private final MethodInfo methodInfo;
private boolean finished; private boolean finished;
private int maxLocals; private int maxLocals;
@ -60,12 +56,8 @@ public final class BufferedCodeBuilder
this.context = context; this.context = context;
this.startLabel = new LabelImpl(this, -1); this.startLabel = new LabelImpl(this, -1);
this.endLabel = new LabelImpl(this, -1); this.endLabel = new LabelImpl(this, -1);
this.original = original;
this.methodInfo = methodInfo; this.methodInfo = methodInfo;
this.maxLocals = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodTypeSymbol()); this.maxLocals = TerminalCodeBuilder.setupTopLocal(methodInfo, original);
if (original != null)
this.maxLocals = Math.max(this.maxLocals, original.maxLocals());
elements.add(startLabel); elements.add(startLabel);
} }
@ -162,27 +154,16 @@ public final class BufferedCodeBuilder
@Override @Override
public List<ExceptionCatch> exceptionHandlers() { public List<ExceptionCatch> exceptionHandlers() {
return elements.stream() return elements.stream()
.filter(x -> x instanceof ExceptionCatch) .<ExceptionCatch>mapMulti((x, sink) -> {
.map(x -> (ExceptionCatch) x) if (x instanceof ExceptionCatch ec) {
sink.accept(ec);
}
})
.toList(); .toList();
} }
@Override int curTopLocal() {
public int maxLocals() { return BufferedCodeBuilder.this.curTopLocal();
for (CodeElement element : elements) {
if (element instanceof LoadInstruction i)
maxLocals = Math.max(maxLocals, i.slot() + i.typeKind().slotSize());
else if (element instanceof StoreInstruction i)
maxLocals = Math.max(maxLocals, i.slot() + i.typeKind().slotSize());
else if (element instanceof IncrementInstruction i)
maxLocals = Math.max(maxLocals, i.slot() + 1);
}
return maxLocals;
}
@Override
public int maxStack() {
throw new UnsupportedOperationException("nyi");
} }
@Override @Override
@ -200,11 +181,6 @@ public final class BufferedCodeBuilder
}); });
} }
@Override
public void writeTo(BufWriterImpl buf) {
DirectCodeBuilder.build(methodInfo, cb -> elements.forEach(cb), constantPool, context, null).writeTo(buf);
}
@Override @Override
public String toString() { public String toString() {
return String.format("CodeModel[id=%s]", Integer.toHexString(System.identityHashCode(this))); return String.format("CodeModel[id=%s]", Integer.toHexString(System.identityHashCode(this)));

View file

@ -103,7 +103,7 @@ public final class BufferedFieldBuilder
@Override @Override
public void writeTo(DirectClassBuilder builder) { public void writeTo(DirectClassBuilder builder) {
builder.withField(name, desc, new Consumer<FieldBuilder>() { builder.withField(name, desc, new Consumer<>() {
@Override @Override
public void accept(FieldBuilder fieldBuilder) { public void accept(FieldBuilder fieldBuilder) {
elements.forEach(fieldBuilder); elements.forEach(fieldBuilder);
@ -111,13 +111,6 @@ public final class BufferedFieldBuilder
}); });
} }
@Override
public void writeTo(BufWriterImpl buf) {
DirectFieldBuilder fb = new DirectFieldBuilder(constantPool, context, name, desc, null);
elements.forEach(fb);
fb.writeTo(buf);
}
@Override @Override
public String toString() { public String toString() {
return String.format("FieldModel[fieldName=%s, fieldType=%s, flags=%d]", name.stringValue(), desc.stringValue(), flags.flagsMask()); return String.format("FieldModel[fieldName=%s, fieldType=%s, flags=%d]", name.stringValue(), desc.stringValue(), flags.flagsMask());

View file

@ -196,7 +196,11 @@ public final class BufferedMethodBuilder
@Override @Override
public Optional<CodeModel> code() { public Optional<CodeModel> code() {
throw new UnsupportedOperationException("nyi"); return elements.stream().<CodeModel>mapMulti((e, sink) -> {
if (e instanceof CodeModel cm) {
sink.accept(cm);
}
}).findFirst();
} }
@Override @Override
@ -209,13 +213,6 @@ public final class BufferedMethodBuilder
}); });
} }
@Override
public void writeTo(BufWriterImpl buf) {
DirectMethodBuilder mb = new DirectMethodBuilder(constantPool, context, name, desc, methodFlags(), null);
elements.forEach(mb);
mb.writeTo(buf);
}
@Override @Override
public String toString() { public String toString() {
return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]", return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]",

View file

@ -43,7 +43,7 @@ import static java.lang.classfile.ClassFile.*;
public final class CodeImpl public final class CodeImpl
extends BoundAttribute.BoundCodeAttribute extends BoundAttribute.BoundCodeAttribute
implements CodeModel, LabelContext { implements LabelContext {
static final Instruction[] SINGLETON_INSTRUCTIONS = new Instruction[256]; static final Instruction[] SINGLETON_INSTRUCTIONS = new Instruction[256];

View file

@ -127,12 +127,10 @@ public final class DirectCodeBuilder
this.transformFwdJumps = transformFwdJumps; this.transformFwdJumps = transformFwdJumps;
this.transformBackJumps = context.shortJumpsOption() == ClassFile.ShortJumpsOption.FIX_SHORT_JUMPS; this.transformBackJumps = context.shortJumpsOption() == ClassFile.ShortJumpsOption.FIX_SHORT_JUMPS;
bytecodesBufWriter = (original instanceof CodeImpl cai) ? new BufWriterImpl(constantPool, context, cai.codeLength()) bytecodesBufWriter = (original instanceof CodeImpl cai) ? new BufWriterImpl(constantPool, context, cai.codeLength())
: new BufWriterImpl(constantPool, context); : new BufWriterImpl(constantPool, context);
this.startLabel = new LabelImpl(this, 0); this.startLabel = new LabelImpl(this, 0);
this.endLabel = new LabelImpl(this, -1); this.endLabel = new LabelImpl(this, -1);
this.topLocal = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodTypeSymbol()); this.topLocal = TerminalCodeBuilder.setupTopLocal(methodInfo, original);
if (original != null)
this.topLocal = Math.max(this.topLocal, original.maxLocals());
} }
@Override @Override
@ -312,8 +310,9 @@ public final class DirectCodeBuilder
private void writeCounters(boolean codeMatch, BufWriterImpl buf) { private void writeCounters(boolean codeMatch, BufWriterImpl buf) {
if (codeMatch) { if (codeMatch) {
buf.writeU2(original.maxStack()); var originalAttribute = (CodeImpl) original;
buf.writeU2(original.maxLocals()); buf.writeU2(originalAttribute.maxStack());
buf.writeU2(originalAttribute.maxLocals());
} else { } else {
StackCounter cntr = StackCounter.of(DirectCodeBuilder.this, buf); StackCounter cntr = StackCounter.of(DirectCodeBuilder.this, buf);
buf.writeU2(cntr.maxStack()); buf.writeU2(cntr.maxStack());

View file

@ -25,8 +25,24 @@
package jdk.internal.classfile.impl; package jdk.internal.classfile.impl;
import java.lang.classfile.CodeBuilder; import java.lang.classfile.CodeBuilder;
import java.lang.classfile.CodeModel;
import java.lang.classfile.attribute.CodeAttribute;
public sealed interface TerminalCodeBuilder extends CodeBuilder, LabelContext public sealed interface TerminalCodeBuilder extends CodeBuilder, LabelContext
permits DirectCodeBuilder, BufferedCodeBuilder { permits DirectCodeBuilder, BufferedCodeBuilder {
int curTopLocal(); int curTopLocal();
static int setupTopLocal(MethodInfo methodInfo, CodeModel original) {
int paramSlots = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodTypeSymbol());
if (original == null) {
return paramSlots;
}
if (original instanceof CodeAttribute attr) {
return Math.max(paramSlots, attr.maxLocals());
}
if (original instanceof BufferedCodeBuilder.Model buffered) {
return Math.max(paramSlots, buffered.curTopLocal());
}
throw new InternalError("Unknown code model " + original);
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,36 +23,48 @@
/* /*
* @test * @test
* @bug 8337225
* @summary Testing ClassFile builder blocks. * @summary Testing ClassFile builder blocks.
* @run junit BuilderBlockTest * @run junit BuilderBlockTest
*/ */
import java.lang.constant.ClassDesc; import java.io.IOException;
import static java.lang.constant.ConstantDescs.*;
import java.lang.constant.MethodTypeDesc;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import helpers.ByteArrayClassLoader;
import java.lang.classfile.AccessFlags; import java.lang.classfile.AccessFlags;
import java.lang.reflect.AccessFlag; import java.lang.classfile.Attributes;
import java.lang.classfile.ClassFile; import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassTransform;
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.CodeElement;
import java.lang.classfile.CodeTransform;
import java.lang.classfile.Label; import java.lang.classfile.Label;
import java.lang.classfile.MethodModel;
import java.lang.classfile.MethodTransform;
import java.lang.classfile.Opcode; import java.lang.classfile.Opcode;
import java.lang.classfile.TypeKind; import java.lang.classfile.TypeKind;
import java.lang.classfile.constantpool.StringEntry;
import java.lang.classfile.instruction.ConstantInstruction;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.reflect.AccessFlag;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.Path;
import helpers.ByteArrayClassLoader;
import jdk.internal.classfile.impl.LabelImpl; import jdk.internal.classfile.impl.LabelImpl;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static java.lang.constant.ConstantDescs.CD_int;
import static java.lang.constant.ConstantDescs.CD_void;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* BuilderBlockTest * BuilderBlockTest
*/ */
class BuilderBlockTest { class BuilderBlockTest {
static final String testClassName = "AdaptCodeTest$TestClass"; static final String testClassName = "BuilderBlockTest$TestClass";
static final Path testClassPath = Paths.get("target/test-classes/" + testClassName + ".class"); static final Path testClassPath = Path.of(URI.create(BuilderBlockTest.class.getResource(testClassName + ".class").toString()));
@Test @Test
void testStartEnd() throws Exception { void testStartEnd() throws Exception {
@ -305,4 +317,81 @@ class BuilderBlockTest {
})); }));
}); });
} }
private static final CodeTransform ALLOCATE_LOCAL_EXAMINER = CodeTransform.ofStateful(() -> new CodeTransform() {
boolean foundItem = false;
@Override
public void atStart(CodeBuilder builder) {
foundItem = false;
}
@Override
public void accept(CodeBuilder cob, CodeElement coe) {
cob.with(coe);
if (coe instanceof ConstantInstruction.LoadConstantInstruction ldc
&& ldc.constantEntry() instanceof StringEntry se
&& se.utf8().equalsString("Output")) {
assertFalse(foundItem);
foundItem = true;
var i = cob.allocateLocal(TypeKind.IntType);
assertEquals(7, i, "Allocated new int slot");
}
}
@Override
public void atEnd(CodeBuilder builder) {
assertTrue(foundItem);
}
});
// Test updating local variable slot management from
// source code models in transformingCode;
// CodeBuilder.transform(CodeModel, CodeTransform) is
// not managed for now
@Test
void testAllocateLocalTransformingCodeAttribute() throws IOException {
var cf = ClassFile.of();
var code = cf.parse(testClassPath)
.methods()
.stream()
.filter(f -> f.methodName().equalsString("work"))
.findFirst()
.orElseThrow()
.findAttribute(Attributes.code())
.orElseThrow();
ClassFile.of().build(ClassDesc.of("Foo"), cb -> cb
.withMethod("foo", MethodTypeDesc.ofDescriptor("(IJI)V"), 0, mb -> mb
.transformCode(code, ALLOCATE_LOCAL_EXAMINER)));
}
@Test
void testAllocateLocalTransformingBufferedCode() throws IOException {
var cf = ClassFile.of();
var testClass = cf.parse(testClassPath);
ClassTransform bufferingTransform = (clb, cle) -> {
if (cle instanceof MethodModel mm && mm.methodName().equalsString("work")) {
clb.withMethodBody(mm.methodName(), mm.methodType(), mm.flags().flagsMask(), cob -> {
int d = cob.allocateLocal(TypeKind.IntType);
int e = cob.allocateLocal(TypeKind.IntType);
assertEquals(5, d);
assertEquals(6, e);
mm.code().ifPresent(code -> code.forEach(cob));
});
}
};
cf.transformClass(testClass, bufferingTransform.andThen(ClassTransform.transformingMethods(MethodTransform.transformingCode(ALLOCATE_LOCAL_EXAMINER))));
}
public static class TestClass {
public void work(int a, long b, int c) {
int d = Math.addExact(a, 25);
int e = Math.multiplyExact(d, c);
System.out.println("Output");
System.out.println(e + b);
throw new IllegalArgumentException("foo");
}
}
} }

View file

@ -39,6 +39,7 @@ import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.parallel.ExecutionMode;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.lang.classfile.attribute.CodeAttribute;
import java.util.*; import java.util.*;
import static helpers.ClassRecord.assertEqualsDeep; import static helpers.ClassRecord.assertEqualsDeep;
@ -222,9 +223,11 @@ class CorpusTest {
var m1 = itStack.next(); var m1 = itStack.next();
var m2 = itNoStack.next(); var m2 = itNoStack.next();
var text1 = m1.methodName().stringValue() + m1.methodType().stringValue() + ": " var text1 = m1.methodName().stringValue() + m1.methodType().stringValue() + ": "
+ m1.code().map(c -> c.maxLocals() + " / " + c.maxStack()).orElse("-"); + m1.code().map(CodeAttribute.class::cast)
.map(c -> c.maxLocals() + " / " + c.maxStack()).orElse("-");
var text2 = m2.methodName().stringValue() + m2.methodType().stringValue() + ": " var text2 = m2.methodName().stringValue() + m2.methodType().stringValue() + ": "
+ m2.code().map(c -> c.maxLocals() + " / " + c.maxStack()).orElse("-"); + m2.code().map(CodeAttribute.class::cast)
.map(c -> c.maxLocals() + " / " + c.maxStack()).orElse("-");
assertEquals(text1, text2); assertEquals(text1, text2);
} }
assertFalse(itNoStack.hasNext()); assertFalse(itNoStack.hasNext());

View file

@ -26,6 +26,7 @@
* @summary Testing ClassFile handling JSR and RET instructions. * @summary Testing ClassFile handling JSR and RET instructions.
* @run junit DiscontinuedInstructionsTest * @run junit DiscontinuedInstructionsTest
*/ */
import java.lang.classfile.attribute.CodeAttribute;
import java.lang.constant.ClassDesc; import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc; import java.lang.constant.MethodTypeDesc;
import java.util.ArrayList; import java.util.ArrayList;
@ -63,7 +64,7 @@ class DiscontinuedInstructionsTest {
.pop() .pop()
.with(DiscontinuedInstruction.RetInstruction.of(355)))); .with(DiscontinuedInstruction.RetInstruction.of(355))));
var c = cc.parse(bytes).methods().get(0).code().get(); var c = (CodeAttribute) cc.parse(bytes).methods().get(0).code().get();
assertEquals(356, c.maxLocals()); assertEquals(356, c.maxLocals());
assertEquals(6, c.maxStack()); assertEquals(6, c.maxStack());

View file

@ -30,6 +30,7 @@
*/ */
import java.lang.classfile.*; import java.lang.classfile.*;
import java.lang.classfile.attribute.CodeAttribute;
import java.lang.classfile.components.ClassPrinter; import java.lang.classfile.components.ClassPrinter;
import java.net.URI; import java.net.URI;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
@ -329,7 +330,7 @@ class StackMapsTest {
var cm = ClassFile.of().parse(bytes); var cm = ClassFile.of().parse(bytes);
for (var method : cm.methods()) { for (var method : cm.methods()) {
var name = method.methodName(); var name = method.methodName();
var code = method.code().orElseThrow(); var code = (CodeAttribute) method.code().orElseThrow();
if (name.equalsString("a")) { if (name.equalsString("a")) {
assertEquals(0, code.maxLocals()); // static method assertEquals(0, code.maxLocals()); // static method
assertEquals(0, code.maxStack()); assertEquals(0, code.maxStack());