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 }