diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index d86baaac362..8be5ae8fa15 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -1384,10 +1384,8 @@ public final class Class 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 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(); } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index dc969c72536..a60958f6f82 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2017,6 +2017,9 @@ public final class System { E[] getEnumConstantsShared(Class klass) { return klass.getEnumConstantsShared(); } + public int classFileVersion(Class clazz) { + return clazz.getClassFileVersion(); + } public void blockedOn(Interruptible b) { Thread.currentThread().blockedOn(b); } diff --git a/src/java.base/share/classes/java/lang/reflect/AccessFlag.java b/src/java.base/share/classes/java/lang/reflect/AccessFlag.java index bb58c440688..4314c8c410d 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessFlag.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessFlag.java @@ -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 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 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. *

diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index b2978d73c8b..2f6f6cca89f 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -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 accessFlags() { - return AccessFlag.maskToAccessFlags(getModifiers(), - AccessFlag.Location.METHOD); + return reflectionFactory.parseAccessFlags(getModifiers(), + AccessFlag.Location.METHOD, + getDeclaringClass()); } /** diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index bffa211fe12..e26d8b03ff8 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -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 accessFlags() { - return AccessFlag.maskToAccessFlags(getModifiers(), AccessFlag.Location.FIELD); + return reflectionFactory.parseAccessFlags(getModifiers(), AccessFlag.Location.FIELD, getDeclaringClass()); } /** diff --git a/src/java.base/share/classes/java/lang/reflect/Parameter.java b/src/java.base/share/classes/java/lang/reflect/Parameter.java index eac02001294..b8a57a9790b 100644 --- a/src/java.base/share/classes/java/lang/reflect/Parameter.java +++ b/src/java.base/share/classes/java/lang/reflect/Parameter.java @@ -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 accessFlags() { - return AccessFlag.maskToAccessFlags(getModifiers(), - AccessFlag.Location.METHOD_PARAMETER); + return AccessibleObject.reflectionFactory.parseAccessFlags(getModifiers(), + AccessFlag.Location.METHOD_PARAMETER, getDeclaringExecutable().getDeclaringClass()); } /** diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 9e11bd7586f..20b6cd23682 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -119,6 +119,12 @@ public interface JavaLangAccess { */ > E[] getEnumConstantsShared(Class 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. */ diff --git a/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java b/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java index 20b390855c9..19be5e53798 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java +++ b/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java @@ -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 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 diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java index 66345855da5..1780eb1297d 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java @@ -59,13 +59,12 @@ public class BasicWriter { } protected Set 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); } } diff --git a/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java b/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java index d87f60a0a1a..d2ee6a2434e 100644 --- a/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java +++ b/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java @@ -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> maskToFlags = new LinkedHashMap<>(); @@ -90,7 +89,7 @@ public class BasicAccessFlagTest { Integer mask = accessFlag.mask(); Set 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 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 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()) {