Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK-8267936: PreserveAllAnnotations option doesn't expose the annotation to Java code #4245

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions make/data/hotspot-symbols/symbols-unix
Expand Up @@ -71,6 +71,7 @@ JVM_GetArrayLength
JVM_GetCallerClass
JVM_GetClassAccessFlags
JVM_GetClassAnnotations
JVM_GetPreserveAllAnnotations
JVM_GetClassConstantPool
JVM_GetClassContext
JVM_GetClassCPEntriesCount
Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/include/jvm.h
Expand Up @@ -561,6 +561,9 @@ JVM_GetClassSignature(JNIEnv *env, jclass cls);
JNIEXPORT jbyteArray JNICALL
JVM_GetClassAnnotations(JNIEnv *env, jclass cls);

JNIEXPORT jboolean JNICALL
JVM_GetPreserveAllAnnotations(JNIEnv *env, jclass cls);

/* Type use annotations support (JDK 1.8) */

JNIEXPORT jbyteArray JNICALL
Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/prims/jvm.cpp
Expand Up @@ -1502,6 +1502,9 @@ JVM_ENTRY(jbyteArray, JVM_GetClassAnnotations(JNIEnv *env, jclass cls))
return NULL;
JVM_END

JVM_ENTRY(jboolean, JVM_GetPreserveAllAnnotations(JNIEnv *env, jclass cls))
return PreserveAllAnnotations;
JVM_END

static bool jvm_get_field_common(jobject field, fieldDescriptor& fd) {
// some of this code was adapted from from jni_FromReflectedField
Expand Down
Expand Up @@ -108,20 +108,23 @@ static Map<Class<? extends Annotation>, Annotation> parseSelectAnnotations(
}
}

private static native boolean preserveAllAnnotations();
private static final boolean preserveAllAnnotations = preserveAllAnnotations();

private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(
byte[] rawAnnotations,
ConstantPool constPool,
Class<?> container,
Class<? extends Annotation>[] selectAnnotationClasses) {
Map<Class<? extends Annotation>, Annotation> result =
new LinkedHashMap<Class<? extends Annotation>, Annotation>();
Map<Class<? extends Annotation>, Annotation> result = new LinkedHashMap<>();
ByteBuffer buf = ByteBuffer.wrap(rawAnnotations);
int numAnnotations = buf.getShort() & 0xFFFF;
for (int i = 0; i < numAnnotations; i++) {
Annotation a = parseAnnotation2(buf, constPool, container, false, selectAnnotationClasses);
if (a != null) {
Class<? extends Annotation> klass = a.annotationType();
if (AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME &&
if (
(preserveAllAnnotations || AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME) &&
result.put(klass, a) != null) {
throw new AnnotationFormatError(
"Duplicate annotation for class: "+klass+": " + a);
Expand Down
7 changes: 7 additions & 0 deletions src/java.base/share/native/libjava/Class.c
Expand Up @@ -100,6 +100,13 @@ Java_java_lang_Class_registerNatives(JNIEnv *env, jclass cls)
sizeof(methods)/sizeof(JNINativeMethod));
}

JNIEXPORT jboolean JNICALL
Java_sun_reflect_annotation_AnnotationParser_preserveAllAnnotations(JNIEnv *env, jclass unused)
{
jboolean t = JVM_GetPreserveAllAnnotations(env, unused);
return t;
}

JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
jboolean initialize, jobject loader, jclass caller)
Expand Down
@@ -0,0 +1,172 @@
/*
* Copyright (c) 2013, 2017, 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
* @summary Expose annotations made RUNTIME visible via Class.getAnnotation when -XX:+PreserveAllAnnotations is on
* @run main/othervm -XX:+PreserveAllAnnotations AnnotationTypeChangedToRuntimeTest
*/

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import static java.lang.annotation.RetentionPolicy.CLASS;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* This test simulates a situation where an existing annotation with
* {@link RetentionPolicy#CLASS CLASS} retention was changed into
* {@link RetentionPolicy#RUNTIME RUNTIME} and we want to read
* its value during runtime with the help of
* -XX:+PreserveAllAnnotations option
*/
public class AnnotationTypeChangedToRuntimeTest {

@Retention(CLASS)
@AnnB
public @interface AnnA_v1 {
}

// An alternative version of AnnA_v1 with CLASS retention instead.
// Used to simulate separate compilation (see AltClassLoader below).
@Retention(RUNTIME)
@AnnB
public @interface AnnA_v2 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AltClassLoader uses AnnA_v2 instead of AnnA_v1 when loading the classes and that makes (in cooperation with -XX:+PreserveAllAnnotations flag) the RuntimeInvisibleAnnotation visible.

}

@Retention(RUNTIME)
@AnnA_v1
public @interface AnnB {
}

@AnnA_v1
public static class TestTask implements Runnable {
@Override
public void run() {
AnnA_v1 ann1 = TestTask.class.getDeclaredAnnotation(AnnA_v1.class);
if (ann1 == null) {
throw new IllegalStateException(
"@" + AnnA_v1.class.getSimpleName() +
" should be visible at runtime");
}
AnnA_v1 ann2 = AnnB.class.getDeclaredAnnotation(AnnA_v1.class);
if (ann2 != null) {
throw new IllegalStateException(
"@" + ann2.annotationType().getSimpleName() +
" found on: " + AnnB.class.getName() +
" should not be visible at runtime");
}
}
}

public static void main(String[] args) throws Exception {
ClassLoader altLoader = new AltClassLoader(
AnnotationTypeChangedToRuntimeTest.class.getClassLoader());

Runnable altTask = (Runnable) Class.forName(
TestTask.class.getName(),
true,
altLoader).newInstance();

altTask.run();
}

/**
* A ClassLoader implementation that loads alternative implementations of
* classes. If class name ends with "_v1" it locates instead a class with
* name ending with "_v2" and loads that class instead.
*/
static class AltClassLoader extends ClassLoader {
AltClassLoader(ClassLoader parent) {
super(parent);
}

@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (name.indexOf('.') < 0) { // root package is our class
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
else { // not our class
return super.loadClass(name, resolve);
}
}

@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
// special class name -> replace it with alternative name
if (name.endsWith("_v1")) {
String altName = name.substring(0, name.length() - 3) + "_v2";
String altPath = altName.replace('.', '/').concat(".class");
try (InputStream is = getResourceAsStream(altPath)) {
if (is != null) {
byte[] bytes = is.readAllBytes();
// patch class bytes to contain original name
for (int i = 0; i < bytes.length - 2; i++) {
if (bytes[i] == '_' &&
bytes[i + 1] == 'v' &&
bytes[i + 2] == '2') {
bytes[i + 2] = '1';
}
}
return defineClass(name, bytes, 0, bytes.length);
}
else {
throw new ClassNotFoundException(name);
}
}
catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
else { // not special class name -> just load the class
String path = name.replace('.', '/').concat(".class");
try (InputStream is = getResourceAsStream(path)) {
if (is != null) {
byte[] bytes = is.readAllBytes();
return defineClass(name, bytes, 0, bytes.length);
}
else {
throw new ClassNotFoundException(name);
}
}
catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
}
}
}
61 changes: 61 additions & 0 deletions test/jdk/java/lang/reflect/TestAllAnnotationsPreserved.java
@@ -0,0 +1,61 @@

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;

/*
* Copyright (c) 2021, 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 8267936
* @summary Expose runtime invisible annotations via Class.getAnnotation when -XX:+PreserveAllAnnotations is on
* @run main/othervm -XX:+PreserveAllAnnotations TestAllAnnotationsPreserved
*/

public class TestAllAnnotationsPreserved {
public static void main(String... args) {
new TestAllAnnotationsPreserved().test();
}

void test() {
NotAtRuntimeByDefault a = Test.class.getAnnotation(NotAtRuntimeByDefault.class);
if (a == null) {
throw new NullPointerException("Annotation NotAtRuntimeByDefault isn't available on class Test!");
}
if (a.value() != 9) {
throw new IllegalStateException("Expecting value 9 from the @NotAtRuntimeByDefault annotation");
}
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
static @interface NotAtRuntimeByDefault {
int value();
}

@NotAtRuntimeByDefault(9)
static class Test {
}
}