8297271: AccessFlag.maskToAccessFlags should be specific to class file version

Reviewed-by: rriggs
This commit is contained in:
Chen Liang 2025-05-01 14:37:26 +00:00
parent 34807df762
commit bee273d6b4
10 changed files with 136 additions and 60 deletions

View File

@ -1384,10 +1384,8 @@ public final class Class<T> implements java.io.Serializable,
isAnonymousClass() || isArray()) ?
AccessFlag.Location.INNER_CLASS :
AccessFlag.Location.CLASS;
return AccessFlag.maskToAccessFlags((location == AccessFlag.Location.CLASS) ?
getClassAccessFlagsRaw() :
getModifiers(),
location);
return getReflectionFactory().parseAccessFlags((location == AccessFlag.Location.CLASS) ?
getClassAccessFlagsRaw() : getModifiers(), location, this);
}
/**
@ -4125,7 +4123,7 @@ public final class Class<T> implements java.io.Serializable,
* type is returned. If the class is a primitive type then the latest class
* file major version is returned and zero is returned for the minor version.
*/
private int getClassFileVersion() {
int getClassFileVersion() {
Class<?> c = isArray() ? elementType() : this;
return c.getClassFileVersion0();
}

View File

@ -2017,6 +2017,9 @@ public final class System {
E[] getEnumConstantsShared(Class<E> klass) {
return klass.getEnumConstantsShared();
}
public int classFileVersion(Class<?> clazz) {
return clazz.getClassFileVersion();
}
public void blockedOn(Interruptible b) {
Thread.currentThread().blockedOn(b);
}

View File

@ -372,18 +372,17 @@ public enum AccessFlag {
/**
* {@return an unmodifiable set of access flags for the given mask value
* appropriate for the location in question}
* appropriate for the location in the current class file format version}
*
* @param mask bit mask of access flags
* @param location context to interpret mask value
* @throws IllegalArgumentException if the mask contains bit
* positions not support for the location in question
* positions not defined for the location in the current class file format
* @throws NullPointerException if {@code location} is {@code null}
*/
public static Set<AccessFlag> maskToAccessFlags(int mask, Location location) {
var definition = findDefinition(location);
int flagsMask = location.flagsMask();
int parsingMask = location == Location.METHOD ? flagsMask | ACC_STRICT : flagsMask; // flagMask lacks strictfp
int unmatchedMask = mask & (~parsingMask);
var definition = findDefinition(location); // null checks location
int unmatchedMask = mask & (~location.flagsMask());
if (unmatchedMask != 0) {
throw new IllegalArgumentException("Unmatched bit position 0x" +
Integer.toHexString(unmatchedMask) +
@ -392,6 +391,30 @@ public enum AccessFlag {
return new AccessFlagSet(definition, mask);
}
/**
* {@return an unmodifiable set of access flags for the given mask value
* appropriate for the location in the given class file format version}
*
* @param mask bit mask of access flags
* @param location context to interpret mask value
* @param cffv the class file format to interpret mask value
* @throws IllegalArgumentException if the mask contains bit
* positions not defined for the location in the given class file format
* @throws NullPointerException if {@code location} or {@code cffv} is {@code null}
* @since 25
*/
public static Set<AccessFlag> maskToAccessFlags(int mask, Location location, ClassFileFormatVersion cffv) {
var definition = findDefinition(location); // null checks location
int unmatchedMask = mask & (~location.flagsMask(cffv)); // null checks cffv
if (unmatchedMask != 0) {
throw new IllegalArgumentException("Unmatched bit position 0x" +
Integer.toHexString(unmatchedMask) +
" for location " + location +
" for class file format " + cffv);
}
return new AccessFlagSet(definition, mask);
}
/**
* A location within a {@code class} file where flags can be applied.
* <p>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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
@ -221,8 +221,9 @@ public abstract sealed class Executable extends AccessibleObject
*/
@Override
public Set<AccessFlag> accessFlags() {
return AccessFlag.maskToAccessFlags(getModifiers(),
AccessFlag.Location.METHOD);
return reflectionFactory.parseAccessFlags(getModifiers(),
AccessFlag.Location.METHOD,
getDeclaringClass());
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 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
@ -218,7 +218,7 @@ class Field extends AccessibleObject implements Member {
*/
@Override
public Set<AccessFlag> accessFlags() {
return AccessFlag.maskToAccessFlags(getModifiers(), AccessFlag.Location.FIELD);
return reflectionFactory.parseAccessFlags(getModifiers(), AccessFlag.Location.FIELD, getDeclaringClass());
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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
@ -172,8 +172,8 @@ public final class Parameter implements AnnotatedElement {
* @since 20
*/
public Set<AccessFlag> accessFlags() {
return AccessFlag.maskToAccessFlags(getModifiers(),
AccessFlag.Location.METHOD_PARAMETER);
return AccessibleObject.reflectionFactory.parseAccessFlags(getModifiers(),
AccessFlag.Location.METHOD_PARAMETER, getDeclaringExecutable().getDeclaringClass());
}
/**

View File

@ -119,6 +119,12 @@ public interface JavaLangAccess {
*/
<E extends Enum<E>> E[] getEnumConstantsShared(Class<E> klass);
/**
* Returns the big-endian packed minor-major version of the class file
* of this class.
*/
int classFileVersion(Class<?> clazz);
/**
* Set current thread's blocker field.
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 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,15 +32,10 @@ import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.lang.classfile.ClassFile;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.*;
import java.util.Set;
import jdk.internal.access.JavaLangReflectAccess;
@ -518,6 +513,31 @@ public class ReflectionFactory {
}
}
public final Set<AccessFlag> parseAccessFlags(int mask, AccessFlag.Location location, Class<?> classFile) {
var cffv = classFileFormatVersion(classFile);
return cffv == null ?
AccessFlag.maskToAccessFlags(mask, location) :
AccessFlag.maskToAccessFlags(mask, location, cffv);
}
private final ClassFileFormatVersion classFileFormatVersion(Class<?> cl) {
int raw = SharedSecrets.getJavaLangAccess().classFileVersion(cl);
int major = raw & 0xFFFF;
int minor = raw >>> Character.SIZE;
assert VM.isSupportedClassFileVersion(major, minor) : major + "." + minor;
if (major >= ClassFile.JAVA_12_VERSION) {
if (minor == 0)
return ClassFileFormatVersion.fromMajor(raw);
return null; // preview or old preview, fallback to default handling
} else if (major == ClassFile.JAVA_1_VERSION) {
return minor < 3 ? ClassFileFormatVersion.RELEASE_0 : ClassFileFormatVersion.RELEASE_1;
}
return ClassFileFormatVersion.fromMajor(major);
}
//--------------------------------------------------------------------------
//
// Internals only below this point

View File

@ -59,13 +59,12 @@ public class BasicWriter {
}
protected Set<AccessFlag> maskToAccessFlagsReportUnknown(int mask, AccessFlag.Location location, ClassFileFormatVersion cffv) {
// TODO pass cffv to maskToAccessFlags
try {
return AccessFlag.maskToAccessFlags(mask, location);
return AccessFlag.maskToAccessFlags(mask, location, cffv);
} catch (IllegalArgumentException ex) {
mask &= location.flagsMask(cffv);
report("Access Flags: " + ex.getMessage());
return AccessFlag.maskToAccessFlags(mask, location);
return AccessFlag.maskToAccessFlags(mask, location, cffv);
}
}

View File

@ -23,11 +23,14 @@
/*
* @test
* @bug 8266670 8293626
* @bug 8266670 8293626 8297271
* @summary Basic tests of AccessFlag
* @run junit BasicAccessFlagTest
*/
import java.lang.classfile.ClassFile;
import java.lang.reflect.AccessFlag;
import java.lang.reflect.ClassFileFormatVersion;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.EnumSet;
@ -36,30 +39,26 @@ import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import static org.junit.jupiter.api.Assertions.*;
public class BasicAccessFlagTest {
public static void main(String... args) throws Exception {
testSourceModifiers();
testMaskOrdering();
testDisjoint();
testMaskToAccessFlagsPositive();
testLocationsNullHandling();
}
/*
* Verify sourceModifier() == true access flags have a
* corresponding constant in java.lang.reflect.Modifier.
*/
private static void testSourceModifiers() throws Exception {
@Test
public void testSourceModifiers() throws Exception {
Class<?> modifierClass = Modifier.class;
for(AccessFlag accessFlag : AccessFlag.values()) {
if (accessFlag.sourceModifier()) {
// Check for consistency
Field f = modifierClass.getField(accessFlag.name());
if (accessFlag.mask() != f.getInt(null) ) {
throw new RuntimeException("Unexpected mask for " +
accessFlag);
}
assertEquals(f.getInt(null), accessFlag.mask(), accessFlag + " mask");
}
}
}
@ -67,22 +66,22 @@ public class BasicAccessFlagTest {
// The mask values of the enum constants must be non-decreasing;
// in other words stay the same (for colliding mask values) or go
// up.
private static void testMaskOrdering() {
@Test
public void testMaskOrdering() {
AccessFlag[] values = AccessFlag.values();
for (int i = 1; i < values.length; i++) {
AccessFlag left = values[i-1];
AccessFlag right = values[i];
if (left.mask() > right.mask()) {
throw new RuntimeException(left
+ "has a greater mask than "
+ right);
}
assertTrue(left.mask() <= right.mask(), () -> left
+ "has a greater mask than "
+ right);
}
}
// Test that if access flags have a matching mask, their locations
// are disjoint.
private static void testDisjoint() {
@Test
public void testDisjoint() {
// First build the mask -> access flags map...
Map<Integer, Set<AccessFlag>> maskToFlags = new LinkedHashMap<>();
@ -90,7 +89,7 @@ public class BasicAccessFlagTest {
Integer mask = accessFlag.mask();
Set<AccessFlag> flags = maskToFlags.get(mask);
if (flags == null ) {
if (flags == null) {
flags = new HashSet<>();
flags.add(accessFlag);
maskToFlags.put(mask, flags);
@ -135,7 +134,8 @@ public class BasicAccessFlagTest {
// For each access flag, make sure it is recognized on every kind
// of location it can apply to
private static void testMaskToAccessFlagsPositive() {
@Test
public void testMaskToAccessFlagsPositive() {
for (var accessFlag : AccessFlag.values()) {
Set<AccessFlag> expectedSet = EnumSet.of(accessFlag);
for (var location : accessFlag.locations()) {
@ -146,17 +146,43 @@ public class BasicAccessFlagTest {
accessFlag + ", " + location);
}
}
for (var cffv : ClassFileFormatVersion.values()) {
for (var location : accessFlag.locations(cffv)) {
Set<AccessFlag> computedSet =
AccessFlag.maskToAccessFlags(accessFlag.mask(), location, cffv);
if (!expectedSet.equals(computedSet)) {
throw new RuntimeException("Bad set computation on " +
accessFlag + ", " + location);
}
}
}
}
assertEquals(Set.of(AccessFlag.STRICT), AccessFlag.maskToAccessFlags(Modifier.STRICT, AccessFlag.Location.METHOD, ClassFileFormatVersion.RELEASE_8));
}
private static void testLocationsNullHandling() {
for (var flag : AccessFlag.values() ) {
try {
flag.locations(null);
throw new RuntimeException("Did not get NPE on " + flag + ".location(null)");
} catch (NullPointerException npe ) {
; // Expected
}
@Test
public void testMaskToAccessFlagsNegative() {
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(Modifier.STRICT, AccessFlag.Location.METHOD));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(Modifier.STRICT, AccessFlag.Location.METHOD, ClassFileFormatVersion.RELEASE_17));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(Modifier.STRICT, AccessFlag.Location.METHOD, ClassFileFormatVersion.RELEASE_1));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(Modifier.PRIVATE, AccessFlag.Location.CLASS));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_MODULE, AccessFlag.Location.CLASS, ClassFileFormatVersion.RELEASE_8));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_ANNOTATION, AccessFlag.Location.CLASS, ClassFileFormatVersion.RELEASE_4));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_ENUM, AccessFlag.Location.FIELD, ClassFileFormatVersion.RELEASE_4));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_SYNTHETIC, AccessFlag.Location.INNER_CLASS, ClassFileFormatVersion.RELEASE_4));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_PUBLIC, AccessFlag.Location.INNER_CLASS, ClassFileFormatVersion.RELEASE_0));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_MANDATED, AccessFlag.Location.METHOD_PARAMETER, ClassFileFormatVersion.RELEASE_7));
assertThrows(IllegalArgumentException.class, () -> AccessFlag.maskToAccessFlags(ClassFile.ACC_MANDATED, AccessFlag.Location.MODULE, ClassFileFormatVersion.RELEASE_7));
}
@Test
public void testLocationsNullHandling() {
for (var flag : AccessFlag.values()) {
assertThrows(NullPointerException.class, () -> flag.locations(null));
}
for (var location : AccessFlag.Location.values()) {
assertThrows(NullPointerException.class, () -> location.flags(null));
}
for (var location : AccessFlag.Location.values()) {