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 }