diff --git a/src/java.base/share/classes/java/lang/classfile/CodeElement.java b/src/java.base/share/classes/java/lang/classfile/CodeElement.java index 9ca51383344..3ad2ff16b35 100644 --- a/src/java.base/share/classes/java/lang/classfile/CodeElement.java +++ b/src/java.base/share/classes/java/lang/classfile/CodeElement.java @@ -27,6 +27,7 @@ package java.lang.classfile; import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.attribute.UnknownAttribute; /** * Marker interface for a member element of a {@link CodeModel}. Such an @@ -49,5 +50,5 @@ import java.lang.classfile.attribute.StackMapTableAttribute; public sealed interface CodeElement extends ClassFileElement permits Instruction, PseudoInstruction, CustomAttribute, RuntimeVisibleTypeAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, - StackMapTableAttribute { + StackMapTableAttribute, UnknownAttribute { } diff --git a/src/java.base/share/classes/java/lang/classfile/CustomAttribute.java b/src/java.base/share/classes/java/lang/classfile/CustomAttribute.java index 9f924588770..c4d34d75dd5 100644 --- a/src/java.base/share/classes/java/lang/classfile/CustomAttribute.java +++ b/src/java.base/share/classes/java/lang/classfile/CustomAttribute.java @@ -36,9 +36,6 @@ import jdk.internal.classfile.impl.TemporaryConstantPool; * by {@link #attributeMapper}, and registered to the {@link * ClassFile.AttributeMapperOption} so the user-defined attributes can be read. *

- * User-defined attributes are currently not delivered in the traversal of a - * {@link CodeModel}. - *

* Accessor methods on user-defined attributes read from {@code class} files * may throw {@link IllegalArgumentException} if the attribute model is lazily * evaluated, and the evaluation encounters malformed {@code class} file format diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/UnknownAttribute.java b/src/java.base/share/classes/java/lang/classfile/attribute/UnknownAttribute.java index c35942bc42c..28a6e9e183a 100644 --- a/src/java.base/share/classes/java/lang/classfile/attribute/UnknownAttribute.java +++ b/src/java.base/share/classes/java/lang/classfile/attribute/UnknownAttribute.java @@ -35,8 +35,6 @@ import jdk.internal.classfile.impl.BoundAttribute; * unknown if it is not recognized by one of the mappers in {@link Attributes} * and is not recognized by the {@link ClassFile.AttributesProcessingOption}. *

- * This attribute is not delivered in the traversal of a {@link CodeModel}. - *

* An unknown attribute may appear anywhere where an attribute may appear, and * has an {@linkplain AttributeStability#UNKNOWN unknown} data dependency. * @@ -45,7 +43,7 @@ import jdk.internal.classfile.impl.BoundAttribute; */ public sealed interface UnknownAttribute extends Attribute, - ClassElement, MethodElement, FieldElement + ClassElement, MethodElement, FieldElement, CodeElement permits BoundAttribute.BoundUnknownAttribute { /** diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java index 114101b02e5..2027d94b2a8 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. 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 @@ -210,6 +210,15 @@ public abstract sealed class BoundAttribute> } return entries; } + + @Override + public void writeTo(BufWriterImpl buf) { + if (buf.canWriteDirect(classReader) && buf.labelsMatch(ctx)) { + classReader.copyBytesTo(buf, payloadStart - NAME_AND_LENGTH_PREFIX, payloadLen() + NAME_AND_LENGTH_PREFIX); + } else { + attributeMapper().writeAttribute(buf, this); + } + } } public static final class BoundSyntheticAttribute extends BoundAttribute diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java index db1300a809a..89451fee819 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java @@ -1,5 +1,5 @@ /* - * 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. * @@ -46,6 +46,7 @@ public final class BufWriterImpl implements BufWriter { private final ConstantPoolBuilder constantPool; private final ClassFileImpl context; private LabelContext labelContext; + private boolean labelsMatch; private final ClassEntry thisClass; private final int majorVersion; byte[] elems; @@ -76,9 +77,17 @@ public final class BufWriterImpl implements BufWriter { return labelContext; } - public void setLabelContext(LabelContext labelContext) { + public void setLabelContext(LabelContext labelContext, boolean labelsMatch) { this.labelContext = labelContext; + this.labelsMatch = labelsMatch; } + + public boolean labelsMatch(LabelContext lc) { + return labelsMatch + && labelContext instanceof DirectCodeBuilder dcb + && dcb.original == lc; + } + @Override public boolean canWriteDirect(ConstantPool other) { return constantPool.canWriteDirect(other); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java index 609411f2e06..286d29029ad 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java @@ -29,6 +29,7 @@ import java.lang.classfile.attribute.CodeAttribute; import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.attribute.UnknownAttribute; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.instruction.*; import java.util.ArrayList; @@ -168,6 +169,7 @@ public final class CodeImpl generateCatchTargets(consumer); if (classReader.context().passDebugElements()) generateDebugElements(consumer); + generateUserAttributes(consumer); for (int pos=codeStart; pos consumer) { + for (var attr : attributes) { + if (attr instanceof CustomAttribute || attr instanceof UnknownAttribute) { + consumer.accept((CodeElement) attr); + } + } + } + public boolean compareCodeBytes(BufWriterImpl buf, int offset, int len) { return codeLength == len && classReader.compare(buf, offset, codeStart, codeLength); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java index bf93fbd9a89..2741d1918ca 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java @@ -354,7 +354,6 @@ public final class DirectCodeBuilder @Override public void writeBody(BufWriterImpl buf) { DirectCodeBuilder dcb = DirectCodeBuilder.this; - buf.setLabelContext(dcb); int codeLength = curPc(); if (codeLength == 0 || codeLength >= 65536) { @@ -366,6 +365,7 @@ public final class DirectCodeBuilder } boolean codeMatch = dcb.original != null && codeAndExceptionsMatch(codeLength); + buf.setLabelContext(dcb, codeMatch); var context = dcb.context; if (context.stackMapsWhenRequired()) { if (codeMatch) { @@ -384,7 +384,7 @@ public final class DirectCodeBuilder buf.writeBytes(dcb.bytecodesBufWriter); dcb.writeExceptionHandlers(buf); dcb.attributes.writeTo(buf); - buf.setLabelContext(null); + buf.setLabelContext(null, false); } @Override diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java b/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java index 749abbed23b..e5af11f3fee 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. 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 @@ -32,4 +32,5 @@ public sealed interface LabelContext Label getLabel(int bci); void setLabelTarget(Label label, int bci); int labelToBci(Label label); + default boolean canWriteDirect(LabelContext original) { return false; } } diff --git a/test/jdk/jdk/classfile/AttributeInCodeTest.java b/test/jdk/jdk/classfile/AttributeInCodeTest.java new file mode 100644 index 00000000000..d82af4993cb --- /dev/null +++ b/test/jdk/jdk/classfile/AttributeInCodeTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. 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 + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.classfile.*; +import java.lang.classfile.attribute.UnknownAttribute; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Objects; + +import org.junit.jupiter.api.Test; + +import static java.lang.classfile.ClassFile.ACC_STATIC; +import static java.lang.constant.ConstantDescs.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @bug 8347472 + * @summary Testing Attribute behavior for those appearing on CodeModel + * @run junit AttributeInCodeTest + */ +class AttributeInCodeTest { + + static final String STRANGE_ATTRIBUTE_NAME = "StrangeAttribute"; + static class StrangeAttribute extends CustomAttribute { + private final Utf8Entry name; + + StrangeAttribute(Utf8Entry name) { + super(STRANGE_ATTRIBUTE_MAPPER); + this.name = name; + } + + @Override + public Utf8Entry attributeName() { + return name; + } + } + + static final AttributeMapper STRANGE_ATTRIBUTE_MAPPER = new AttributeMapper<>() { + + @Override + public String name() { + return STRANGE_ATTRIBUTE_NAME; + } + + @Override + public StrangeAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + return new StrangeAttribute(cf.readEntry(pos - 6, Utf8Entry.class)); + } + + @Override + public void writeAttribute(BufWriter buf, StrangeAttribute attr) { + buf.writeIndex(attr.name); + buf.writeInt(0); + } + + @Override + public AttributeMapper.AttributeStability stability() { + return AttributeMapper.AttributeStability.STATELESS; + } + }; + + @Test + void testUserAttributeDelivery() { + var strangeAwareCf = ClassFile.of(ClassFile.AttributeMapperOption.of(utf8 -> { + if (utf8.equalsString(STRANGE_ATTRIBUTE_NAME)) return STRANGE_ATTRIBUTE_MAPPER; + return null; + })); + var cpb = ConstantPoolBuilder.of(); + var entry = cpb.utf8Entry(STRANGE_ATTRIBUTE_NAME); + int pos = entry.index(); + var classBytes = ClassFile.of().build(cpb.classEntry(ClassDesc.of("StrangeClass")), cpb, clb -> clb + .withMethodBody("dummy", MTD_void, ACC_STATIC, cob -> cob.return_().with(new StrangeAttribute(entry)))); + + // read unknown + var withUnknown = ClassFile.of().parse(classBytes).methods().getFirst() + .code().orElseThrow().elementStream() + .filter(e -> e instanceof Attribute).toList(); + assertEquals(1, withUnknown.size()); + UnknownAttribute unknown = (UnknownAttribute) withUnknown.getFirst(); + assertEquals(pos, unknown.attributeName().index()); + assertArrayEquals(new byte[0], unknown.contents()); + + // read known + var withKnown = strangeAwareCf.parse(classBytes).methods().getFirst() + .code().orElseThrow().elementStream() + .filter(e -> e instanceof Attribute).toList(); + assertEquals(1, withKnown.size()); + StrangeAttribute strange = (StrangeAttribute) withKnown.getFirst(); + assertEquals(pos, strange.attributeName().index()); + } + + // Verifies reusing stack maps updates the label indices. + @Test + void testStackMapReuse() throws Throwable { + ClassModel exampleClass; + try (var is = Objects.requireNonNull(AttributeInCodeTest.class.getResourceAsStream("/ExampleClass.class"))) { + exampleClass = ClassFile.of().parse(is.readAllBytes()); + } + + var code = exampleClass.methods().getFirst() + .findAttribute(Attributes.code()).orElseThrow(); + var stackMap = code + .findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(1, stackMap.entries().size()); + var transform = ClassTransform.transformingMethodBodies(new CodeTransform() { + @Override + public void accept(CodeBuilder builder, CodeElement element) { + builder.with(element); + } + + @Override + public void atStart(CodeBuilder builder) { + var ps = ClassDesc.of("java.io.PrintStream"); + builder.getstatic(ClassDesc.of("java.lang.System"), "out", ps) + .ldc("Injected") + .invokevirtual(ps, "println", MethodTypeDesc.of(CD_void, CD_String)) + .with(stackMap); + } + }); + + var cf = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS); + var bytes = cf.transformClass(exampleClass, transform); + assertEquals(List.of(), cf.verify(bytes)); + var l = MethodHandles.lookup().defineHiddenClass(bytes, true, MethodHandles.Lookup.ClassOption.STRONG); + var mh = l.findConstructor(l.lookupClass(), MethodType.methodType(void.class, String.class)).asType(MethodType.methodType(Object.class, String.class)); + Object _ = mh.invokeExact((String) "ape"); + } +} + +class ExampleClass { + ExampleClass(String s) { + if (s.isEmpty()) { + System.out.println("Empty"); + } + // Frame here + System.out.println(s.length()); + } +} diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 895c04704cc..b66bd8f1c00 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. 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 @@ -59,7 +59,7 @@ class RebuildingTransformation { case RuntimeVisibleTypeAnnotationsAttribute a -> fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); case SignatureAttribute a -> fb.with(SignatureAttribute.of(Signature.parseFrom(a.asTypeSignature().signatureString()))); case SyntheticAttribute a -> fb.with(SyntheticAttribute.of()); - case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName().stringValue()); } } @@ -91,7 +91,7 @@ class RebuildingTransformation { case RuntimeVisibleTypeAnnotationsAttribute a -> mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); case SignatureAttribute a -> mb.with(SignatureAttribute.of(MethodSignature.parseFrom(a.asMethodSignature().signatureString()))); case SyntheticAttribute a -> mb.with(SyntheticAttribute.of()); - case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName().stringValue()); } } @@ -142,7 +142,7 @@ class RebuildingTransformation { case SourceFileAttribute a -> clb.with(SourceFileAttribute.of(a.sourceFile().stringValue())); case SourceIDAttribute a -> clb.with(SourceIDAttribute.of(a.sourceId().stringValue())); case SyntheticAttribute a -> clb.with(SyntheticAttribute.of()); - case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName().stringValue()); } } @@ -594,8 +594,10 @@ class RebuildingTransformation { StackMapFrameInfo.of(labels.computeIfAbsent(fr.target(), l -> cob.newLabel()), transformFrameTypeInfos(fr.locals(), cob, labels), transformFrameTypeInfos(fr.stack(), cob, labels))).toList())); - case CustomAttribute a -> + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName().stringValue()); + case UnknownAttribute a -> + throw new AssertionError("Unexpected unknown attribute: " + a.attributeName().stringValue()); } } }