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 step
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(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(SUPER,
122 Set.of(AccessFlag.Location.CLASS),
123 ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES),
124
125 new StepFunctionTC(IDENTITY,
126 Set.of(),
127 ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES),
128
129 new StepFunctionTC(STRICT_INIT,
130 Set.of(),
131 ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES),
132
133 new StepFunctionTC(OPEN,
134 Set.of(),
135 ClassFileFormatVersion.RELEASE_9),
136
137 new StepFunctionTC(TRANSITIVE,
138 Set.of(),
139 ClassFileFormatVersion.RELEASE_9),
140
141 new StepFunctionTC(STATIC_PHASE,
142 Set.of(),
143 ClassFileFormatVersion.RELEASE_9),
144
145 new StepFunctionTC(BRIDGE,
146 Set.of(),
147 ClassFileFormatVersion.RELEASE_5),
148
149 new StepFunctionTC(VARARGS,
150 Set.of(),
151 ClassFileFormatVersion.RELEASE_5),
152
153 new StepFunctionTC(INTERFACE,
154 removeInnerClass(INTERFACE.locations()),
155 ClassFileFormatVersion.RELEASE_1),
156
157 new StepFunctionTC(ABSTRACT,
158 removeInnerClass(ABSTRACT.locations()),
159 ClassFileFormatVersion.RELEASE_1),
160
161 new StepFunctionTC(ANNOTATION,
162 Set.of(),
163 ClassFileFormatVersion.RELEASE_5),
164
165 new StepFunctionTC(ENUM,
166 Set.of(),
167 ClassFileFormatVersion.RELEASE_5),
168
169 new StepFunctionTC(MODULE,
170 Set.of(),
171 ClassFileFormatVersion.RELEASE_9)
172 };
173
174 for (var testCase : testCases) {
175 for (var cffv : ClassFileFormatVersion.values()) {
176 compareLocations(cffv.compareTo(testCase.transition()) >= 0 ?
177 testCase.finalLocs() :
178 testCase.initialLocs(),
179 testCase.accessFlag, cffv);
180 }
181 }
182 }
183
184 private static void compareLocations(Set<AccessFlag.Location> expected,
185 AccessFlag accessFlag,
186 ClassFileFormatVersion cffv) {
187 var actual = accessFlag.locations(cffv);
188 if (!expected.equals(actual)) {
189 throw new RuntimeException("Unexpected locations for " +
190 accessFlag + " on " + cffv + "\n" +
191 "Expected " + expected + "; got \t" + actual);
192 }
193 }
194
195 private static Set<AccessFlag.Location> removeInnerClass(Set<AccessFlag.Location> locations) {
196 var s = new HashSet<>(locations);
197 s.remove(Location.INNER_CLASS);
198 return s;
199 }
200
201 private record StepFunctionTC(AccessFlag accessFlag,
202 Set<AccessFlag.Location> initialLocs,
203 ClassFileFormatVersion transition) {
204
205 public Set<AccessFlag.Location> finalLocs() {
206 return accessFlag.locations(ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES);
207 }
208 }
209
210
211 private record TwoStepFunctionTC(AccessFlag accessFlag,
212 Set<AccessFlag.Location> initialLocs,
213 ClassFileFormatVersion transition1,
214 Set<AccessFlag.Location> firstLocs,
215 ClassFileFormatVersion transition2) {
216
217 public Set<AccessFlag.Location> secondLocs() {
218 return accessFlag.locations();
219 }
220 }
221
222 private static void testTwoStepAccessFlags() {
223 TwoStepFunctionTC[] testCases = {
224 new TwoStepFunctionTC(FINAL,
225 Set.of(Location.CLASS, Location.FIELD, Location.METHOD),
226 ClassFileFormatVersion.RELEASE_1,
227 Set.of(Location.CLASS, Location.FIELD, Location.METHOD, Location.INNER_CLASS),
228 ClassFileFormatVersion.RELEASE_8),
229
230 new TwoStepFunctionTC(MANDATED,
231 Set.of(),
232 ClassFileFormatVersion.RELEASE_8,
233 Set.of(Location.METHOD_PARAMETER),
234 ClassFileFormatVersion.RELEASE_9),
235 };
236
237 for (var testCase : testCases) {
238 for (var cffv : ClassFileFormatVersion.values()) {
239 var transition1 = testCase.transition1();
240 var transition2 = testCase.transition2();
241 Set<AccessFlag.Location> expected;
242 if (cffv.compareTo(transition1) < 0) {
243 expected = testCase.initialLocs();
244 } else if (cffv.compareTo(transition1) >= 0 &&
245 cffv.compareTo(transition2) < 0) {
246 expected = testCase.firstLocs();
247 } else { // cffv >= transition2
248 expected = testCase.secondLocs();
249 }
250
251 compareLocations(expected, testCase.accessFlag(), cffv);
252 }
253 }
254 }
255
256 private static void testSynthetic() {
257 for (var cffv : ClassFileFormatVersion.values()) {
258 Set<AccessFlag.Location> expected;
259 if (cffv.compareTo(ClassFileFormatVersion.RELEASE_5) < 0) {
260 expected = Set.of();
261 } else {
262 expected =
263 switch(cffv) {
264 case RELEASE_5, RELEASE_6,
265 RELEASE_7 -> Set.of(Location.CLASS, Location.FIELD,
266 Location.METHOD,
267 Location.INNER_CLASS);
268 case RELEASE_8 -> Set.of(Location.CLASS, Location.FIELD,
269 Location.METHOD,
270 Location.INNER_CLASS,
271 Location.METHOD_PARAMETER);
272 default -> SYNTHETIC.locations();
273 };
274 }
275 compareLocations(expected, SYNTHETIC, cffv);
276 }
277 }
278
279 private static void testStrict() {
280 for (var cffv : ClassFileFormatVersion.values()) {
281 Set<AccessFlag.Location> expected =
282 (cffv.compareTo(ClassFileFormatVersion.RELEASE_2) >= 0 &&
283 cffv.compareTo(ClassFileFormatVersion.RELEASE_16) <= 0) ?
284 Set.of(Location.METHOD) :
285 Set.of();
286 compareLocations(expected, STRICT, cffv);
287 }
288 }
289
290 private static void testLatestMatch() {
291 // Verify accessFlag.locations() and
292 // accessFlag.locations(ClassFileFormatVersion.latest()) are
293 // consistent
294 var LATEST = ClassFileFormatVersion.latest();
295 for (var accessFlag : AccessFlag.values()) {
296 var locationSet = accessFlag.locations();
297 var locationLatestSet = accessFlag.locations(LATEST);
298 if (!locationSet.equals(locationLatestSet)) {
299 throw new RuntimeException("Unequal location sets for " + accessFlag);
300 }
301 }
302 }
303
304 private static void testFlagVersionConsistency() {
305 for (var flag : AccessFlag.values()) {
306 for (var location : AccessFlag.Location.values()) {
307 if (location.flags().contains(flag) != flag.locations().contains(location)) {
308 throw new RuntimeException(String.format("AccessFlag and Location inconsistency:" +
309 "flag %s and location %s are inconsistent for the latest version", flag, location));
310 }
311 }
312 }
313 for (var cffv : ClassFileFormatVersion.values()) {
314 for (var flag : AccessFlag.values()) {
315 for (var location : AccessFlag.Location.values()) {
316 if (location.flags(cffv).contains(flag) != flag.locations(cffv).contains(location)) {
317 throw new RuntimeException(String.format("AccessFlag and Location inconsistency:" +
318 "flag %s and location %s are inconsistent for class file version %s", flag, location, cffv));
319 }
320 }
321 }
322 }
323 }
324
325 private static void testLocationMaskFlagConsistency() {
326 for (var location : AccessFlag.Location.values()) {
327 if (!flagsAndMaskMatch(location.flags(), location.flagsMask())) {
328 throw new RuntimeException(String.format("Flags and mask mismatch for %s", location));
329 }
330 for (var cffv : ClassFileFormatVersion.values()) {
331 if (!flagsAndMaskMatch(location.flags(cffv), location.flagsMask(cffv))) {
332 throw new RuntimeException(String.format("Flags and mask mismatch for %s in %s", location, cffv));
333 }
334 }
335 }
336 }
337
338 private static boolean flagsAndMaskMatch(Set<AccessFlag> flags, int mask) {
339 for (var flag : flags) {
340 int bit = flag.mask();
341 if (((mask & bit) == 0))
342 return false;
343 mask &= ~bit;
344 }
345 return mask == 0;
346 }
347 }