mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-28 11:53:09 +00:00
8347472: Correct Attribute traversal and writing for Code attributes
Reviewed-by: asotona
This commit is contained in:
parent
83c7d3bbe8
commit
7bb8b1768f
@ -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 {
|
||||
}
|
||||
|
||||
@ -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.
|
||||
* <p>
|
||||
* User-defined attributes are currently not delivered in the traversal of a
|
||||
* {@link CodeModel}.
|
||||
* <p>
|
||||
* 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
|
||||
|
||||
@ -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}.
|
||||
* <p>
|
||||
* This attribute is not delivered in the traversal of a {@link CodeModel}.
|
||||
* <p>
|
||||
* 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<UnknownAttribute>,
|
||||
ClassElement, MethodElement, FieldElement
|
||||
ClassElement, MethodElement, FieldElement, CodeElement
|
||||
permits BoundAttribute.BoundUnknownAttribute {
|
||||
|
||||
/**
|
||||
|
||||
@ -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<T extends Attribute<T>>
|
||||
}
|
||||
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<SyntheticAttribute>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<codeEnd; ) {
|
||||
if (labels[pos - codeStart] != null)
|
||||
consumer.accept(labels[pos - codeStart]);
|
||||
@ -204,6 +206,14 @@ public final class CodeImpl
|
||||
return exceptionTable;
|
||||
}
|
||||
|
||||
private void generateUserAttributes(Consumer<? super CodeElement> 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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
165
test/jdk/jdk/classfile/AttributeInCodeTest.java
Normal file
165
test/jdk/jdk/classfile/AttributeInCodeTest.java
Normal file
@ -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<StrangeAttribute> {
|
||||
private final Utf8Entry name;
|
||||
|
||||
StrangeAttribute(Utf8Entry name) {
|
||||
super(STRANGE_ATTRIBUTE_MAPPER);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Utf8Entry attributeName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
static final AttributeMapper<StrangeAttribute> 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());
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user