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