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 }