8315575: Retransform of record class with record component annotation fails with CFE

Reviewed-by: sspitsyn, coleenp
This commit is contained in:
Alex Menkov 2024-03-26 23:54:28 +00:00
parent 2725405ac9
commit 8fc9097b37
5 changed files with 198 additions and 18 deletions

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024, 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
@ -3408,8 +3408,7 @@ u4 ClassFileParser::parse_classfile_record_attribute(const ClassFileStream* cons
CHECK_0);
RecordComponent* record_component =
RecordComponent::allocate(_loader_data, name_index, descriptor_index,
attributes_count, generic_sig_index,
RecordComponent::allocate(_loader_data, name_index, descriptor_index, generic_sig_index,
annotations, type_annotations, CHECK_0);
record_components->at_put(x, record_component);
} // End of component processing loop

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, 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
@ -33,12 +33,11 @@
RecordComponent* RecordComponent::allocate(ClassLoaderData* loader_data,
u2 name_index, u2 descriptor_index,
u2 attributes_count,
u2 generic_signature_index,
AnnotationArray* annotations,
AnnotationArray* type_annotations, TRAPS) {
return new (loader_data, size(), MetaspaceObj::RecordComponentType, THREAD)
RecordComponent(name_index, descriptor_index, attributes_count,
RecordComponent(name_index, descriptor_index,
generic_signature_index, annotations, type_annotations);
}
@ -65,7 +64,6 @@ void RecordComponent::print_value_on(outputStream* st) const {
void RecordComponent::print_on(outputStream* st) const {
st->print("name_index: %d", _name_index);
st->print(" - descriptor_index: %d", _descriptor_index);
st->print(" - attributes_count: %d", _attributes_count);
if (_generic_signature_index != 0) {
st->print(" - generic_signature_index: %d", _generic_signature_index);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, 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
@ -36,25 +36,21 @@ class RecordComponent: public MetaspaceObj {
AnnotationArray* _type_annotations;
u2 _name_index;
u2 _descriptor_index;
u2 _attributes_count;
// generic_signature_index gets set if the Record component has a Signature
// attribute. A zero value indicates that there was no Signature attribute.
u2 _generic_signature_index;
public:
RecordComponent(u2 name_index, u2 descriptor_index, u2 attributes_count,
u2 generic_signature_index, AnnotationArray* annotations,
AnnotationArray* type_annotations):
RecordComponent(u2 name_index, u2 descriptor_index, u2 generic_signature_index,
AnnotationArray* annotations, AnnotationArray* type_annotations):
_annotations(annotations), _type_annotations(type_annotations),
_name_index(name_index), _descriptor_index(descriptor_index),
_attributes_count(attributes_count),
_generic_signature_index(generic_signature_index) { }
// Allocate instance of this class
static RecordComponent* allocate(ClassLoaderData* loader_data,
u2 name_index, u2 descriptor_index,
u2 attributes_count,
u2 generic_signature_index,
AnnotationArray* annotations,
AnnotationArray* type_annotations, TRAPS);
@ -69,8 +65,6 @@ class RecordComponent: public MetaspaceObj {
_descriptor_index = descriptor_index;
}
u2 attributes_count() const { return _attributes_count; }
u2 generic_signature_index() const { return _generic_signature_index; }
void set_generic_signature_index(u2 generic_signature_index) {
_generic_signature_index = generic_signature_index;

@ -494,7 +494,6 @@ void JvmtiClassFileReconstituter::write_record_attribute() {
RecordComponent* component = components->at(x);
if (component->generic_signature_index() != 0) {
length += 8; // Signature attribute size
assert(component->attributes_count() > 0, "Bad component attributes count");
}
if (component->annotations() != nullptr) {
length += 6 + component->annotations()->length();
@ -511,7 +510,11 @@ void JvmtiClassFileReconstituter::write_record_attribute() {
RecordComponent* component = components->at(i);
write_u2(component->name_index());
write_u2(component->descriptor_index());
write_u2(component->attributes_count());
u2 attributes_count = (component->generic_signature_index() != 0 ? 1 : 0)
+ (component->annotations() != nullptr ? 1 : 0)
+ (component->type_annotations() != nullptr ? 1 : 0);
write_u2(attributes_count);
if (component->generic_signature_index() != 0) {
write_signature_attribute(component->generic_signature_index());
}

@ -0,0 +1,186 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8315575
* @summary test that records with invisible annotation can be retransformed
*
* @library /test/lib
* @run shell MakeJAR.sh retransformAgent
* @run main/othervm -javaagent:retransformAgent.jar -Xlog:redefine+class=trace RetransformRecordAnnotation
*/
import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.instrument.ClassFileTransformer;
import java.nio.file.Files;
import java.security.ProtectionDomain;
public class RetransformRecordAnnotation extends AInstrumentationTestCase {
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeTypeAnno {}
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeParamAnno {
String s() default "foo";
}
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@interface ClassTypeAnno {}
@Retention(RetentionPolicy.CLASS)
@interface ClassParamAnno {
String s() default "bar";
}
@RuntimeTypeAnno
@RuntimeParamAnno(s = "1")
public record VisibleAnnos(@RuntimeTypeAnno @RuntimeParamAnno(s = "2") Object o, Object other) {
}
@ClassTypeAnno
@ClassParamAnno(s = "3")
public record InvisibleAnnos(@ClassTypeAnno @ClassParamAnno(s = "4") Object o, Object other) {
}
@RuntimeTypeAnno
@RuntimeParamAnno(s = "5")
@ClassTypeAnno
@ClassParamAnno(s = "6")
public record MixedAnnos(@RuntimeTypeAnno @RuntimeParamAnno(s = "7")
@ClassTypeAnno @ClassParamAnno(s = "8") Object o, Object other) {
}
public static void main (String[] args) throws Throwable {
ATestCaseScaffold test = new RetransformRecordAnnotation();
test.beVerbose();
test.runTest();
}
private Transformer transformer;
public RetransformRecordAnnotation() throws Throwable {
super("RetransformRecordAnnotation");
}
private void log(Object o) {
System.out.println(String.valueOf(o));
}
// Retransforms target class using provided class bytes;
private void retransform(Class targetClass, byte[] classBytes) throws Throwable {
transformer.prepare(targetClass, classBytes);
fInst.retransformClasses(targetClass);
assertTrue(targetClass.getName() + " was not seen by transform()",
transformer.getSeenClassBytes() != null);
}
protected final void doRunTest() throws Throwable {
transformer = new Transformer();
fInst.addTransformer(transformer, true);
{
log("Sanity: retransform to original class bytes");
retransform(InvisibleAnnos.class, loadClassBytes(InvisibleAnnos.class));
log("");
}
// The following testcases use null as new class bytes (i.e. no transform is performed).
// However, it is enough for testing purposes as the JvmtiClassFileReconstituter is still involved
// in preparation of the initial class bytes.
{
log("Test: retransform VisibleAnnos to null");
retransform(VisibleAnnos.class, null);
log("");
}
{
log("Test: retransform InvisibleAnnos to null");
retransform(InvisibleAnnos.class, null);
log("");
}
{
log("Test: retransform MixedAnnos to null");
retransform(MixedAnnos.class, null);
log("");
}
}
private byte[] loadClassBytes(Class cls) throws Exception {
String classFileName = cls.getName() + ".class";
File classFile = new File(System.getProperty("test.classes", "."), classFileName);
log("Reading test class from " + classFile);
byte[] classBytes = Files.readAllBytes(classFile.toPath());
log("Read " + classBytes.length + " bytes.");
return classBytes;
}
public class Transformer implements ClassFileTransformer {
private String targetClassName;
private byte[] seenClassBytes;
private byte[] newClassBytes;
public Transformer() {
}
// Prepares transformer for Instrumentation.retransformClasses.
public void prepare(Class targetClass, byte[] classBytes) {
targetClassName = targetClass.getName();
newClassBytes = classBytes;
seenClassBytes = null;
}
byte[] getSeenClassBytes() {
return seenClassBytes;
}
public String toString() {
return Transformer.this.getClass().getName();
}
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (className.equals(targetClassName)) {
log(this + ".transform() sees '" + className
+ "' of " + classfileBuffer.length + " bytes.");
seenClassBytes = classfileBuffer;
if (newClassBytes != null) {
log(this + ".transform() sets new classbytes for '" + className
+ "' of " + newClassBytes.length + " bytes.");
}
return newClassBytes;
}
return null;
}
}
}