1 /*
2  * Copyright 2014 - 2020 Rafael Winterhalter
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package net.bytebuddy;
17
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import net.bytebuddy.build.CachedReturnPlugin;
20 import net.bytebuddy.build.HashCodeAndEqualsPlugin;
21 import net.bytebuddy.description.type.TypeDescription;
22 import net.bytebuddy.dynamic.ClassFileLocator;
23 import net.bytebuddy.utility.OpenedClassReader;
24 import net.bytebuddy.jar.asm.Opcodes;
25
26 import java.io.IOException;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31
32 /**
33  * A wrapper object for representing a validated class file version in the format that is specified by the
34  * <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html">JVMS</a>.
35  */

36 @HashCodeAndEqualsPlugin.Enhance
37 public class ClassFileVersion implements Comparable<ClassFileVersion> {
38
39     /**
40      * Returns the minimal version number that is legal.
41      */

42     protected static final int BASE_VERSION = 44;
43
44     /**
45      * The class file version of Java 1.
46      */

47     public static final ClassFileVersion JAVA_V1 = new ClassFileVersion(Opcodes.V1_1);
48
49     /**
50      * The class file version of Java 2.
51      */

52     public static final ClassFileVersion JAVA_V2 = new ClassFileVersion(Opcodes.V1_2);
53
54     /**
55      * The class file version of Java 3.
56      */

57     public static final ClassFileVersion JAVA_V3 = new ClassFileVersion(Opcodes.V1_3);
58
59     /**
60      * The class file version of Java 4.
61      */

62     public static final ClassFileVersion JAVA_V4 = new ClassFileVersion(Opcodes.V1_4);
63
64     /**
65      * The class file version of Java 5.
66      */

67     public static final ClassFileVersion JAVA_V5 = new ClassFileVersion(Opcodes.V1_5);
68
69     /**
70      * The class file version of Java 6.
71      */

72     public static final ClassFileVersion JAVA_V6 = new ClassFileVersion(Opcodes.V1_6);
73
74     /**
75      * The class file version of Java 7.
76      */

77     public static final ClassFileVersion JAVA_V7 = new ClassFileVersion(Opcodes.V1_7);
78
79     /**
80      * The class file version of Java 8.
81      */

82     public static final ClassFileVersion JAVA_V8 = new ClassFileVersion(Opcodes.V1_8);
83
84     /**
85      * The class file version of Java 9.
86      */

87     public static final ClassFileVersion JAVA_V9 = new ClassFileVersion(Opcodes.V9);
88
89     /**
90      * The class file version of Java 10.
91      */

92     public static final ClassFileVersion JAVA_V10 = new ClassFileVersion(Opcodes.V10);
93
94     /**
95      * The class file version of Java 11.
96      */

97     public static final ClassFileVersion JAVA_V11 = new ClassFileVersion(Opcodes.V11);
98
99     /**
100      * The class file version of Java 12.
101      */

102     public static final ClassFileVersion JAVA_V12 = new ClassFileVersion(Opcodes.V12);
103
104     /**
105      * The class file version of Java 13.
106      */

107     public static final ClassFileVersion JAVA_V13 = new ClassFileVersion(Opcodes.V13);
108
109     /**
110      * The class file version of Java 14.
111      */

112     public static final ClassFileVersion JAVA_V14 = new ClassFileVersion(Opcodes.V14);
113
114     /**
115      * The class file version of Java 15.
116      */

117     public static final ClassFileVersion JAVA_V15 = new ClassFileVersion(Opcodes.V15);
118
119     /**
120      * The class file version of Java 16.
121      */

122     public static final ClassFileVersion JAVA_V16 = new ClassFileVersion(Opcodes.V16);
123
124     /**
125      * A version locator for the executing JVM.
126      */

127     private static final VersionLocator VERSION_LOCATOR = AccessController.doPrivileged(VersionLocator.CreationAction.INSTANCE);
128
129     /**
130      * The version number that is represented by this class file version instance.
131      */

132     private final int versionNumber;
133
134     /**
135      * Creates a wrapper for a given minor-major release of the Java class file file.
136      *
137      * @param versionNumber The minor-major release number.
138      */

139     protected ClassFileVersion(int versionNumber) {
140         this.versionNumber = versionNumber;
141     }
142
143     /**
144      * Creates a wrapper for a given minor-major release of the Java class file file.
145      *
146      * @param versionNumber The minor-major release number.
147      * @return A representation of the version number.
148      */

149     public static ClassFileVersion ofMinorMajor(int versionNumber) {
150         ClassFileVersion classFileVersion = new ClassFileVersion(versionNumber);
151         if (classFileVersion.getMajorVersion() <= BASE_VERSION) {
152             throw new IllegalArgumentException("Class version " + versionNumber + " is not valid");
153         }
154         return classFileVersion;
155     }
156
157     /**
158      * Returns the Java class file by its representation by a version string in accordance to the formats known to <i>javac</i>.
159      *
160      * @param javaVersionString The Java version string.
161      * @return The appropriate class file version.
162      */

163     public static ClassFileVersion ofJavaVersionString(String javaVersionString) {
164         if (javaVersionString.equals("1.1")) {
165             return JAVA_V1;
166         } else if (javaVersionString.equals("1.2")) {
167             return JAVA_V2;
168         } else if (javaVersionString.equals("1.3")) {
169             return JAVA_V3;
170         } else if (javaVersionString.equals("1.4")) {
171             return JAVA_V4;
172         } else if (javaVersionString.equals("1.5") || javaVersionString.equals("5")) {
173             return JAVA_V5;
174         } else if (javaVersionString.equals("1.6") || javaVersionString.equals("6")) {
175             return JAVA_V6;
176         } else if (javaVersionString.equals("1.7") || javaVersionString.equals("7")) {
177             return JAVA_V7;
178         } else if (javaVersionString.equals("1.8") || javaVersionString.equals("8")) {
179             return JAVA_V8;
180         } else if (javaVersionString.equals("1.9") || javaVersionString.equals("9")) {
181             return JAVA_V9;
182         } else if (javaVersionString.equals("1.10") || javaVersionString.equals("10")) {
183             return JAVA_V10;
184         } else if (javaVersionString.equals("1.11") || javaVersionString.equals("11")) {
185             return JAVA_V11;
186         } else if (javaVersionString.equals("1.12") || javaVersionString.equals("12")) {
187             return JAVA_V12;
188         } else if (javaVersionString.equals("1.13") || javaVersionString.equals("13")) {
189             return JAVA_V13;
190         } else if (javaVersionString.equals("1.14") || javaVersionString.equals("14")) {
191             return JAVA_V14;
192         } else if (javaVersionString.equals("1.15") || javaVersionString.equals("15")) {
193             return JAVA_V15;
194         } else if (javaVersionString.equals("1.16") || javaVersionString.equals("16")) {
195             return JAVA_V16;
196         } else {
197             if (OpenedClassReader.EXPERIMENTAL) {
198                 try {
199                     int version = Integer.parseInt(javaVersionString.startsWith("1.")
200                             ? javaVersionString.substring(2)
201                             : javaVersionString);
202                     if (version > 0) {
203                         return new ClassFileVersion(BASE_VERSION + version);
204                     }
205                 } catch (NumberFormatException ignored) {
206                 }
207             }
208             throw new IllegalArgumentException("Unknown Java version string: " + javaVersionString);
209         }
210     }
211
212     /**
213      * Creates a class file version for a given major release of Java. Currently, all versions reaching from
214      * Java 1 to Java 9 are supported.
215      *
216      * @param javaVersion The Java version.
217      * @return A wrapper for the given Java class file version.
218      */

219     public static ClassFileVersion ofJavaVersion(int javaVersion) {
220         switch (javaVersion) {
221             case 1:
222                 return JAVA_V1;
223             case 2:
224                 return JAVA_V2;
225             case 3:
226                 return JAVA_V3;
227             case 4:
228                 return JAVA_V4;
229             case 5:
230                 return JAVA_V5;
231             case 6:
232                 return JAVA_V6;
233             case 7:
234                 return JAVA_V7;
235             case 8:
236                 return JAVA_V8;
237             case 9:
238                 return JAVA_V9;
239             case 10:
240                 return JAVA_V10;
241             case 11:
242                 return JAVA_V11;
243             case 12:
244                 return JAVA_V12;
245             case 13:
246                 return JAVA_V13;
247             case 14:
248                 return JAVA_V14;
249             case 15:
250                 return JAVA_V15;
251             case 16:
252                 return JAVA_V16;
253             default:
254                 if (OpenedClassReader.EXPERIMENTAL && javaVersion > 0) {
255                     return new ClassFileVersion(BASE_VERSION + javaVersion);
256                 } else {
257                     throw new IllegalArgumentException("Unknown Java version: " + javaVersion);
258                 }
259         }
260     }
261
262     /**
263      * Finds the highest class file version that is compatible to the current JVM version. Prior to Java 9, this is achieved
264      * by parsing the {@code java.version} property which is provided by {@link java.lang.System#getProperty(String)}. If the system
265      * property is not available, an {@link IllegalStateException} is thrown.
266      *
267      * @return The currently running Java process's class file version.
268      */

269     @CachedReturnPlugin.Enhance
270     public static ClassFileVersion ofThisVm() {
271         return VERSION_LOCATOR.locate();
272     }
273
274     /**
275      * Finds the highest class file version that is compatible to the current JVM version. Prior to Java 9, this is achieved
276      * by parsing the {@code java.version} property which is provided by {@link java.lang.System#getProperty(String)}. If the system
277      * property is not available, the {@code fallback} version is returned.
278      *
279      * @param fallback The version to fallback to if locating a class file version is not possible.
280      * @return The currently running Java process's class file version or the fallback if locating this version is impossible.
281      */

282     @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
283     public static ClassFileVersion ofThisVm(ClassFileVersion fallback) {
284         try {
285             return ofThisVm();
286         } catch (Exception ignored) {
287             return fallback;
288         }
289     }
290
291     /**
292      * Extracts a classclass version. The classbyte code is located by querying the {@link ClassLoader} of the class.
293      *
294      * @param type The type for which to locate a class file version.
295      * @return The type's class file version.
296      * @throws IOException If an error occurs while reading the class file.
297      */

298     public static ClassFileVersion of(Class<?> type) throws IOException {
299         return of(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader()));
300     }
301
302     /**
303      * Extracts a classclass version.
304      *
305      * @param type             The type for which to locate a class file version.
306      * @param classFileLocator The class file locator to query for a class file.
307      * @return The type's class file version.
308      * @throws IOException If an error occurs while reading the class file.
309      */

310     public static ClassFileVersion of(Class<?> type, ClassFileLocator classFileLocator) throws IOException {
311         return of(TypeDescription.ForLoadedType.of(type), classFileLocator);
312     }
313
314     /**
315      * Extracts a classclass version.
316      *
317      * @param typeDescription  The type for which to locate a class file version.
318      * @param classFileLocator The class file locator to query for a class file.
319      * @return The type's class file version.
320      * @throws IOException If an error occurs while reading the class file.
321      */

322     public static ClassFileVersion of(TypeDescription typeDescription, ClassFileLocator classFileLocator) throws IOException {
323         return ofClassFile(classFileLocator.locate(typeDescription.getName()).resolve());
324     }
325
326     /**
327      * Extracts a classclass version from a class file.
328      *
329      * @param binaryRepresentation The class file's binary representation.
330      * @return The supplied class file's class file version.
331      */

332     public static ClassFileVersion ofClassFile(byte[] binaryRepresentation) {
333         if (binaryRepresentation.length < 7) {
334             throw new IllegalArgumentException("Supplied byte array is too short to be a class file with " + binaryRepresentation.length + byte");
335         }
336         return ofMinorMajor(binaryRepresentation[6] << 8 | binaryRepresentation[7] & 0xFF);
337     }
338
339     /**
340      * Returns the minor-major release number of this class file version.
341      *
342      * @return The minor-major release number of this class file version.
343      */

344     public int getMinorMajorVersion() {
345         return versionNumber;
346     }
347
348     /**
349      * Returns the major version this instance represents.
350      *
351      * @return The major version this instance represents.
352      */

353     public short getMajorVersion() {
354         return (short) (versionNumber & 0xFF);
355     }
356
357     /**
358      * Returns the minor version this instance represents.
359      *
360      * @return The minor version this instance represents.
361      */

362     public short getMinorVersion() {
363         return (short) (versionNumber >> 16);
364     }
365
366     /**
367      * Returns the Java runtime version number of this class file version.
368      *
369      * @return The Java runtime version.
370      */

371     public int getJavaVersion() {
372         return getMajorVersion() - BASE_VERSION;
373     }
374
375     /**
376      * Checks if this class file version is at least as new as the provided version.
377      *
378      * @param classFileVersion The version to check against.
379      * @return {@code trueif this version is at least of the given version.
380      */

381     public boolean isAtLeast(ClassFileVersion classFileVersion) {
382         return compareTo(classFileVersion) > -1;
383     }
384
385     /**
386      * Checks if this class file version is newer than the provided version.
387      *
388      * @param classFileVersion The version to check against.
389      * @return {@code trueif this version is newer than the provided version.
390      */

391     public boolean isGreaterThan(ClassFileVersion classFileVersion) {
392         return compareTo(classFileVersion) > 0;
393     }
394
395     /**
396      * Checks if this class file version is at most as new as the provided version.
397      *
398      * @param classFileVersion The version to check against.
399      * @return {@code trueif this version is as most as new as the provided version.
400      */

401     public boolean isAtMost(ClassFileVersion classFileVersion) {
402         return compareTo(classFileVersion) < 1;
403     }
404
405     /**
406      * Checks if this class file version is older than the provided version.
407      *
408      * @param classFileVersion The version to check against.
409      * @return {@code trueif this version is older than the provided version.
410      */

411     public boolean isLessThan(ClassFileVersion classFileVersion) {
412         return compareTo(classFileVersion) < 0;
413     }
414
415     /**
416      * Returns this class file version indicating a class using preview features.
417      *
418      * @return This class file version but indicating the use of preview features.
419      */

420     public ClassFileVersion asPreviewVersion() {
421         return new ClassFileVersion(versionNumber | Opcodes.V_PREVIEW);
422     }
423
424     /**
425      * Returns {@code trueif this class file version indicates the use of preview features.
426      *
427      * @return {@code trueif this class file version indicates the use of preview features.
428      */

429     public boolean isPreviewVersion() {
430         return (versionNumber & Opcodes.V_PREVIEW) == Opcodes.V_PREVIEW;
431     }
432
433     /**
434      * {@inheritDoc}
435      */

436     public int compareTo(ClassFileVersion other) {
437         return Integer.signum(getMajorVersion() == other.getMajorVersion()
438                 ? getMinorVersion() - other.getMinorVersion()
439                 : getMajorVersion() - other.getMajorVersion());
440     }
441
442     @Override
443     public String toString() {
444         return "Java " + getJavaVersion() + " (" + getMinorMajorVersion() + ")";
445     }
446
447     /**
448      * A locator for the executing VM's Java version.
449      */

450     protected interface VersionLocator {
451
452         /**
453          * Locates the current VM's major version number.
454          *
455          * @return The current VM's major version number.
456          */

457         ClassFileVersion locate();
458
459         /**
460          * A creation action for a version locator.
461          */

462         enum CreationAction implements PrivilegedAction<VersionLocator> {
463
464             /**
465              * The singleton instance.
466              */

467             INSTANCE;
468
469             /**
470              * {@inheritDoc}
471              */

472             @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
473             public VersionLocator run() {
474                 try {
475                     return new VersionLocator.ForJava9CapableVm(Runtime.class.getMethod("version"),
476                             Class.forName("java.lang.Runtime$Version").getMethod("major"));
477                 } catch (Exception ignored) {
478                     return VersionLocator.ForLegacyVm.INSTANCE;
479                 }
480             }
481         }
482
483         /**
484          * A version locator for a JVM of at least version 9.
485          */

486         @HashCodeAndEqualsPlugin.Enhance
487         class ForJava9CapableVm implements VersionLocator {
488
489             /**
490              * Indicates that a reflective method call invokes a static method.
491              */

492             private static final Object STATIC_METHOD = null;
493
494             /**
495              * The {@code java.lang.Runtime#version()} method.
496              */

497             private final Method current;
498
499             /**
500              * The {@code java.lang.Runtime.Version#major()} method.
501              */

502             private final Method major;
503
504             /**
505              * Creates a new version locator for a Java 9 capable VM.
506              *
507              * @param current The {@code java.lang.Runtime#version()} method.
508              * @param major   The {@code java.lang.Runtime.Version#major()} method.
509              */

510             protected ForJava9CapableVm(Method current, Method major) {
511                 this.current = current;
512                 this.major = major;
513             }
514
515             /**
516              * {@inheritDoc}
517              */

518             public ClassFileVersion locate() {
519                 try {
520                     return ClassFileVersion.ofJavaVersion((Integer) major.invoke(current.invoke(STATIC_METHOD)));
521                 } catch (IllegalAccessException exception) {
522                     throw new IllegalStateException("Could not access VM version lookup", exception);
523                 } catch (InvocationTargetException exception) {
524                     throw new IllegalStateException("Could not look up VM version", exception.getCause());
525                 }
526             }
527         }
528
529         /**
530          * A version locator for a JVM that does not provide the {@code java.lang.Runtime.Version} class.
531          */

532         enum ForLegacyVm implements VersionLocator, PrivilegedAction<String> {
533
534             /**
535              * The singleton instance.
536              */

537             INSTANCE;
538
539             /**
540              * The system property for this JVM's Java version.
541              */

542             private static final String JAVA_VERSION_PROPERTY = "java.version";
543
544             /**
545              * {@inheritDoc}
546              */

547             public ClassFileVersion locate() {
548                 String versionString = AccessController.doPrivileged(this);
549                 int[] versionIndex = {-1, 0, 0};
550                 for (int i = 1; i < 3; i++) {
551                     versionIndex[i] = versionString.indexOf('.', versionIndex[i - 1] + 1);
552                     if (versionIndex[i] == -1) {
553                         throw new IllegalStateException("This JVM's version string does not seem to be valid: " + versionString);
554                     }
555                 }
556                 return ClassFileVersion.ofJavaVersion(Integer.parseInt(versionString.substring(versionIndex[1] + 1, versionIndex[2])));
557             }
558
559             /**
560              * {@inheritDoc}
561              */

562             public String run() {
563                 return System.getProperty(JAVA_VERSION_PROPERTY);
564             }
565         }
566     }
567 }
568