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());
}
}
}