jdk-24/test/jdk/java/lang/reflect/AccessFlag/VersionedLocationsTest.java
Joe Darcy 1dc5039fed 8293626: AccessFlag::locations(ClassFileFormatVersion cffv) does not throw NPEx when parameter is null
8293627: AccessFlag::locations(ClassFileFormatVersion cffv) and locations() results are inconsistent

Reviewed-by: mchung
2022-09-13 16:51:01 +00:00

289 lines
11 KiB
Java

/*
* Copyright (c) 2022, 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 8289106 8293627
* @summary Tests of AccessFlag.locations(ClassFileFormatVersion)
*/
import java.lang.reflect.AccessFlag;
import static java.lang.reflect.AccessFlag.*;
import java.lang.reflect.ClassFileFormatVersion;
import java.util.HashSet;
import java.util.Set;
/*
* There are several patterns of access flag applicability. First, an
* access flag can be applied to the same set of locations for each
* class file format version. This is "invariant" usage. Second, an
* access flag can be defined for version N, therefore inapplicable
* for earlier versions, and then applied to the same locations for
* all subsequent versions. This is "step" usage. Finally, an access
* flag to have a more complicated pattern, having multiple steps of
* being allowed at more locations or even having locations removed if
* the access flag is retired.
*
* List of access flags and how they are tested:
*
* PUBLIC step
* PRIVATE step
* PROTECTED step
* STATIC step
* FINAL two-step
* SUPER invariant
* OPEN step
* TRANSITIVE step
* SYNCHRONIZED invariant
* STATIC_PHASE step
* VOLATILE invariant
* BRIDGE step
* TRANSIENT invariant
* VARARGS step
* NATIVE invariant
* INTERFACE step
* ABSTRACT step
* STRICT other
* SYNTHETIC other (three-step)
* ANNOTATION step
* ENUM step
* MANDATED two-step
* MODULE step
*/
public class VersionedLocationsTest {
public static void main(String... args) throws Exception {
testInvariantAccessFlags();
testStepFunctionAccessFlags();
testTwoStepAccessFlags();
testSynthetic();
testStrict();
testLatestMatch();
}
/**
* Invariant access flags have the same set of locations for each
* class file format version.
*/
private static void testInvariantAccessFlags() {
Set<AccessFlag> invariantAccessFlags =
Set.of(SUPER, SYNCHRONIZED, VOLATILE, TRANSIENT, NATIVE);
for(var accessFlag : invariantAccessFlags) {
Set<AccessFlag.Location> expected = accessFlag.locations();
for(var cffv : ClassFileFormatVersion.values()) {
compareLocations(accessFlag.locations(), accessFlag, cffv);
}
}
}
private static void testStepFunctionAccessFlags() {
StepFunctionTC[] testCases = {
new StepFunctionTC(PUBLIC,
removeInnerClass(PUBLIC.locations()),
ClassFileFormatVersion.RELEASE_1),
new StepFunctionTC(PRIVATE,
removeInnerClass(PRIVATE.locations()),
ClassFileFormatVersion.RELEASE_1),
new StepFunctionTC(PROTECTED,
removeInnerClass(PROTECTED.locations()),
ClassFileFormatVersion.RELEASE_1),
new StepFunctionTC(STATIC,
removeInnerClass(STATIC.locations()),
ClassFileFormatVersion.RELEASE_1),
new StepFunctionTC(OPEN,
Set.of(),
ClassFileFormatVersion.RELEASE_9),
new StepFunctionTC(TRANSITIVE,
Set.of(),
ClassFileFormatVersion.RELEASE_9),
new StepFunctionTC(STATIC_PHASE,
Set.of(),
ClassFileFormatVersion.RELEASE_9),
new StepFunctionTC(BRIDGE,
Set.of(),
ClassFileFormatVersion.RELEASE_5),
new StepFunctionTC(VARARGS,
Set.of(),
ClassFileFormatVersion.RELEASE_5),
new StepFunctionTC(INTERFACE,
removeInnerClass(INTERFACE.locations()),
ClassFileFormatVersion.RELEASE_1),
new StepFunctionTC(ABSTRACT,
removeInnerClass(ABSTRACT.locations()),
ClassFileFormatVersion.RELEASE_1),
new StepFunctionTC(ANNOTATION,
Set.of(),
ClassFileFormatVersion.RELEASE_5),
new StepFunctionTC(ENUM,
Set.of(),
ClassFileFormatVersion.RELEASE_5),
new StepFunctionTC(MODULE,
Set.of(),
ClassFileFormatVersion.RELEASE_9)
};
for (var testCase : testCases) {
for (var cffv : ClassFileFormatVersion.values()) {
compareLocations(cffv.compareTo(testCase.transition()) >= 0 ?
testCase.finalLocs() :
testCase.initialLocs(),
testCase.accessFlag, cffv);
}
}
}
private static void compareLocations(Set<AccessFlag.Location> expected,
AccessFlag accessFlag,
ClassFileFormatVersion cffv) {
var actual = accessFlag.locations(cffv);
if (!expected.equals(actual)) {
throw new RuntimeException("Unexpected locations for " +
accessFlag + " on " + cffv + "\n" +
"Expected " + expected + "; got \t" + actual);
}
}
private static Set<AccessFlag.Location> removeInnerClass(Set<AccessFlag.Location> locations) {
var s = new HashSet<>(locations);
s.remove(Location.INNER_CLASS);
return s;
}
private record StepFunctionTC(AccessFlag accessFlag,
Set<AccessFlag.Location> initialLocs,
ClassFileFormatVersion transition) {
public Set<AccessFlag.Location> finalLocs() {
return accessFlag.locations();
}
}
private record TwoStepFunctionTC(AccessFlag accessFlag,
Set<AccessFlag.Location> initialLocs,
ClassFileFormatVersion transition1,
Set<AccessFlag.Location> firstLocs,
ClassFileFormatVersion transition2) {
public Set<AccessFlag.Location> secondLocs() {
return accessFlag.locations();
}
}
private static void testTwoStepAccessFlags() {
TwoStepFunctionTC[] testCases = {
new TwoStepFunctionTC(FINAL,
Set.of(Location.CLASS, Location.FIELD, Location.METHOD),
ClassFileFormatVersion.RELEASE_1,
Set.of(Location.CLASS, Location.FIELD, Location.METHOD, Location.INNER_CLASS),
ClassFileFormatVersion.RELEASE_8),
new TwoStepFunctionTC(MANDATED,
Set.of(),
ClassFileFormatVersion.RELEASE_8,
Set.of(Location.METHOD_PARAMETER),
ClassFileFormatVersion.RELEASE_9),
};
for (var testCase : testCases) {
for (var cffv : ClassFileFormatVersion.values()) {
var transition1 = testCase.transition1();
var transition2 = testCase.transition2();
Set<AccessFlag.Location> expected;
if (cffv.compareTo(transition1) < 0) {
expected = testCase.initialLocs();
} else if (cffv.compareTo(transition1) >= 0 &&
cffv.compareTo(transition2) < 0) {
expected = testCase.firstLocs();
} else { // cffv >= transition2
expected = testCase.secondLocs();
}
compareLocations(expected, testCase.accessFlag(), cffv);
}
}
}
private static void testSynthetic() {
for (var cffv : ClassFileFormatVersion.values()) {
Set<AccessFlag.Location> expected;
if (cffv.compareTo(ClassFileFormatVersion.RELEASE_6) <= 0) {
expected = Set.of();
} else {
expected =
switch(cffv) {
case RELEASE_7 -> Set.of(Location.CLASS, Location.FIELD,
Location.METHOD,
Location.INNER_CLASS);
case RELEASE_8 -> Set.of(Location.CLASS, Location.FIELD,
Location.METHOD,
Location.INNER_CLASS,
Location.METHOD_PARAMETER);
default -> SYNTHETIC.locations();
};
}
compareLocations(expected, SYNTHETIC, cffv);
}
}
private static void testStrict() {
for (var cffv : ClassFileFormatVersion.values()) {
Set<AccessFlag.Location> expected =
(cffv.compareTo(ClassFileFormatVersion.RELEASE_2) >= 0 &&
cffv.compareTo(ClassFileFormatVersion.RELEASE_16) <= 0) ?
Set.of(Location.METHOD) :
Set.of();
compareLocations(expected, STRICT, cffv);
}
}
private static void testLatestMatch() {
// Verify accessFlag.locations() and
// accessFlag.locations(ClassFileFormatVersion.latest()) are
// consistent
var LATEST = ClassFileFormatVersion.latest();
for (var accessFlag : AccessFlag.values()) {
var locationSet = accessFlag.locations();
var locationLatestSet = accessFlag.locations(LATEST);
if (!locationSet.equals(locationLatestSet)) {
throw new RuntimeException("Unequal location sets for " + accessFlag);
}
}
}
}