1 /* 2 * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8289106 8293627 27 * @summary Tests of AccessFlag.locations(ClassFileFormatVersion) and 28 * accessors on AccessFlag.Location 29 */ 30 31 import java.lang.reflect.AccessFlag; 32 import static java.lang.reflect.AccessFlag.*; 33 import java.lang.reflect.ClassFileFormatVersion; 34 import java.util.HashSet; 35 import java.util.Set; 36 37 /* 38 * There are several patterns of access flag applicability. First, an 39 * access flag can be applied to the same set of locations for each 40 * class file format version. This is "invariant" usage. Second, an 41 * access flag can be defined for version N, therefore inapplicable 42 * for earlier versions, and then applied to the same locations for 43 * all subsequent versions. This is "step" usage. Finally, an access 44 * flag to have a more complicated pattern, having multiple steps of 45 * being allowed at more locations or even having locations removed if 46 * the access flag is retired. 47 * 48 * List of access flags and how they are tested: 49 * 50 * PUBLIC step 51 * PRIVATE step 52 * PROTECTED step 53 * STATIC step 54 * FINAL two-step 55 * SUPER invariant 56 * OPEN step 57 * TRANSITIVE step 58 * SYNCHRONIZED invariant 59 * STATIC_PHASE step 60 * VOLATILE invariant 61 * BRIDGE step 62 * TRANSIENT invariant 63 * VARARGS step 64 * NATIVE invariant 65 * INTERFACE step 66 * ABSTRACT step 67 * STRICT other 68 * SYNTHETIC other (three-step) 69 * ANNOTATION step 70 * ENUM step 71 * MANDATED two-step 72 * MODULE step 73 */ 74 75 public class VersionedLocationsTest { 76 public static void main(String... args) throws Exception { 77 testInvariantAccessFlags(); 78 testStepFunctionAccessFlags(); 79 testTwoStepAccessFlags(); 80 testSynthetic(); 81 testStrict(); 82 testLatestMatch(); 83 testFlagVersionConsistency(); 84 testLocationMaskFlagConsistency(); 85 } 86 87 /** 88 * Invariant access flags have the same set of locations for each 89 * class file format version. 90 */ 91 private static void testInvariantAccessFlags() { 92 Set<AccessFlag> invariantAccessFlags = 93 Set.of(SUPER, SYNCHRONIZED, VOLATILE, TRANSIENT, NATIVE); 94 for(var accessFlag : invariantAccessFlags) { 95 Set<AccessFlag.Location> expected = accessFlag.locations(); 96 97 for(var cffv : ClassFileFormatVersion.values()) { 98 compareLocations(accessFlag.locations(), accessFlag, cffv); 99 } 100 } 101 } 102 103 private static void testStepFunctionAccessFlags() { 104 StepFunctionTC[] testCases = { 105 new StepFunctionTC(PUBLIC, 106 removeInnerClass(PUBLIC.locations()), 107 ClassFileFormatVersion.RELEASE_1), 108 109 new StepFunctionTC(PRIVATE, 110 removeInnerClass(PRIVATE.locations()), 111 ClassFileFormatVersion.RELEASE_1), 112 113 new StepFunctionTC(PROTECTED, 114 removeInnerClass(PROTECTED.locations()), 115 ClassFileFormatVersion.RELEASE_1), 116 117 new StepFunctionTC(STATIC, 118 removeInnerClass(STATIC.locations()), 119 ClassFileFormatVersion.RELEASE_1), 120 121 new StepFunctionTC(OPEN, 122 Set.of(), 123 ClassFileFormatVersion.RELEASE_9), 124 125 new StepFunctionTC(TRANSITIVE, 126 Set.of(), 127 ClassFileFormatVersion.RELEASE_9), 128 129 new StepFunctionTC(STATIC_PHASE, 130 Set.of(), 131 ClassFileFormatVersion.RELEASE_9), 132 133 new StepFunctionTC(BRIDGE, 134 Set.of(), 135 ClassFileFormatVersion.RELEASE_5), 136 137 new StepFunctionTC(VARARGS, 138 Set.of(), 139 ClassFileFormatVersion.RELEASE_5), 140 141 new StepFunctionTC(INTERFACE, 142 removeInnerClass(INTERFACE.locations()), 143 ClassFileFormatVersion.RELEASE_1), 144 145 new StepFunctionTC(ABSTRACT, 146 removeInnerClass(ABSTRACT.locations()), 147 ClassFileFormatVersion.RELEASE_1), 148 149 new StepFunctionTC(ANNOTATION, 150 Set.of(), 151 ClassFileFormatVersion.RELEASE_5), 152 153 new StepFunctionTC(ENUM, 154 Set.of(), 155 ClassFileFormatVersion.RELEASE_5), 156 157 new StepFunctionTC(MODULE, 158 Set.of(), 159 ClassFileFormatVersion.RELEASE_9) 160 }; 161 162 for (var testCase : testCases) { 163 for (var cffv : ClassFileFormatVersion.values()) { 164 compareLocations(cffv.compareTo(testCase.transition()) >= 0 ? 165 testCase.finalLocs() : 166 testCase.initialLocs(), 167 testCase.accessFlag, cffv); 168 } 169 } 170 } 171 172 private static void compareLocations(Set<AccessFlag.Location> expected, 173 AccessFlag accessFlag, 174 ClassFileFormatVersion cffv) { 175 var actual = accessFlag.locations(cffv); 176 if (!expected.equals(actual)) { 177 throw new RuntimeException("Unexpected locations for " + 178 accessFlag + " on " + cffv + "\n" + 179 "Expected " + expected + "; got \t" + actual); 180 } 181 } 182 183 private static Set<AccessFlag.Location> removeInnerClass(Set<AccessFlag.Location> locations) { 184 var s = new HashSet<>(locations); 185 s.remove(Location.INNER_CLASS); 186 return s; 187 } 188 189 private record StepFunctionTC(AccessFlag accessFlag, 190 Set<AccessFlag.Location> initialLocs, 191 ClassFileFormatVersion transition) { 192 193 public Set<AccessFlag.Location> finalLocs() { 194 return accessFlag.locations(); 195 } 196 } 197 198 199 private record TwoStepFunctionTC(AccessFlag accessFlag, 200 Set<AccessFlag.Location> initialLocs, 201 ClassFileFormatVersion transition1, 202 Set<AccessFlag.Location> firstLocs, 203 ClassFileFormatVersion transition2) { 204 205 public Set<AccessFlag.Location> secondLocs() { 206 return accessFlag.locations(); 207 } 208 } 209 210 private static void testTwoStepAccessFlags() { 211 TwoStepFunctionTC[] testCases = { 212 new TwoStepFunctionTC(FINAL, 213 Set.of(Location.CLASS, Location.FIELD, Location.METHOD), 214 ClassFileFormatVersion.RELEASE_1, 215 Set.of(Location.CLASS, Location.FIELD, Location.METHOD, Location.INNER_CLASS), 216 ClassFileFormatVersion.RELEASE_8), 217 218 new TwoStepFunctionTC(MANDATED, 219 Set.of(), 220 ClassFileFormatVersion.RELEASE_8, 221 Set.of(Location.METHOD_PARAMETER), 222 ClassFileFormatVersion.RELEASE_9), 223 }; 224 225 for (var testCase : testCases) { 226 for (var cffv : ClassFileFormatVersion.values()) { 227 var transition1 = testCase.transition1(); 228 var transition2 = testCase.transition2(); 229 Set<AccessFlag.Location> expected; 230 if (cffv.compareTo(transition1) < 0) { 231 expected = testCase.initialLocs(); 232 } else if (cffv.compareTo(transition1) >= 0 && 233 cffv.compareTo(transition2) < 0) { 234 expected = testCase.firstLocs(); 235 } else { // cffv >= transition2 236 expected = testCase.secondLocs(); 237 } 238 239 compareLocations(expected, testCase.accessFlag(), cffv); 240 } 241 } 242 } 243 244 private static void testSynthetic() { 245 for (var cffv : ClassFileFormatVersion.values()) { 246 Set<AccessFlag.Location> expected; 247 if (cffv.compareTo(ClassFileFormatVersion.RELEASE_5) < 0) { 248 expected = Set.of(); 249 } else { 250 expected = 251 switch(cffv) { 252 case RELEASE_5, RELEASE_6, 253 RELEASE_7 -> Set.of(Location.CLASS, Location.FIELD, 254 Location.METHOD, 255 Location.INNER_CLASS); 256 case RELEASE_8 -> Set.of(Location.CLASS, Location.FIELD, 257 Location.METHOD, 258 Location.INNER_CLASS, 259 Location.METHOD_PARAMETER); 260 default -> SYNTHETIC.locations(); 261 }; 262 } 263 compareLocations(expected, SYNTHETIC, cffv); 264 } 265 } 266 267 private static void testStrict() { 268 for (var cffv : ClassFileFormatVersion.values()) { 269 Set<AccessFlag.Location> expected = 270 (cffv.compareTo(ClassFileFormatVersion.RELEASE_2) >= 0 && 271 cffv.compareTo(ClassFileFormatVersion.RELEASE_16) <= 0) ? 272 Set.of(Location.METHOD) : 273 Set.of(); 274 compareLocations(expected, STRICT, cffv); 275 } 276 } 277 278 private static void testLatestMatch() { 279 // Verify accessFlag.locations() and 280 // accessFlag.locations(ClassFileFormatVersion.latest()) are 281 // consistent 282 var LATEST = ClassFileFormatVersion.latest(); 283 for (var accessFlag : AccessFlag.values()) { 284 var locationSet = accessFlag.locations(); 285 var locationLatestSet = accessFlag.locations(LATEST); 286 if (!locationSet.equals(locationLatestSet)) { 287 throw new RuntimeException("Unequal location sets for " + accessFlag); 288 } 289 } 290 } 291 292 private static void testFlagVersionConsistency() { 293 for (var flag : AccessFlag.values()) { 294 for (var location : AccessFlag.Location.values()) { 295 if (location.flags().contains(flag) != flag.locations().contains(location)) { 296 throw new RuntimeException(String.format("AccessFlag and Location inconsistency:" + 297 "flag %s and location %s are inconsistent for the latest version")); 298 } 299 } 300 } 301 for (var cffv : ClassFileFormatVersion.values()) { 302 for (var flag : AccessFlag.values()) { 303 for (var location : AccessFlag.Location.values()) { 304 if (location.flags(cffv).contains(flag) != flag.locations(cffv).contains(location)) { 305 throw new RuntimeException(String.format("AccessFlag and Location inconsistency:" + 306 "flag %s and location %s are inconsistent for class file version %s")); 307 } 308 } 309 } 310 } 311 } 312 313 private static void testLocationMaskFlagConsistency() { 314 for (var location : AccessFlag.Location.values()) { 315 if (!flagsAndMaskMatch(location.flags(), location.flagsMask())) { 316 throw new RuntimeException(String.format("Flags and mask mismatch for %s", location)); 317 } 318 for (var cffv : ClassFileFormatVersion.values()) { 319 if (!flagsAndMaskMatch(location.flags(cffv), location.flagsMask(cffv))) { 320 throw new RuntimeException(String.format("Flags and mask mismatch for %s in %s", location, cffv)); 321 } 322 } 323 } 324 } 325 326 private static boolean flagsAndMaskMatch(Set<AccessFlag> flags, int mask) { 327 for (var flag : flags) { 328 int bit = flag.mask(); 329 if (((mask & bit) == 0)) 330 return false; 331 mask &= ~bit; 332 } 333 return mask == 0; 334 } 335 }