From 6acf032db891875c6a7403a18d5dc7c552fb4c11 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Mon, 1 May 2023 17:56:04 +0000 Subject: [PATCH] 8306678: Replace use of os.version with an internal Version record Reviewed-by: mchung --- .../internal/loader/ClassLoaderHelper.java | 22 ++-- .../jdk/internal/util/OperatingSystem.java | 31 ++++- .../jdk/internal/util/StaticProperty.java | 11 ++ .../classes/jdk/internal/util/Version.java | 114 ++++++++++++++++++ test/jdk/jdk/internal/util/OSTest.java | 11 ++ test/jdk/jdk/internal/util/VersionTest.java | 103 ++++++++++++++++ 6 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/util/Version.java create mode 100644 test/jdk/jdk/internal/util/VersionTest.java diff --git a/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java b/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java index 2e0e15e69a2..4ee692b8410 100644 --- a/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java +++ b/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,21 +27,15 @@ package jdk.internal.loader; import java.io.File; import java.util.ArrayList; -import sun.security.action.GetPropertyAction; + +import jdk.internal.util.OperatingSystem; +import jdk.internal.util.Version; class ClassLoaderHelper { - private static final boolean hasDynamicLoaderCache; - static { - String osVersion = GetPropertyAction.privilegedGetProperty("os.version"); - // dynamic linker cache support on os.version >= 11.x - int major = 11; - int i = osVersion.indexOf('.'); - try { - major = Integer.parseInt(i < 0 ? osVersion : osVersion.substring(0, i)); - } catch (NumberFormatException e) {} - // SDK 10.15 and earlier always reports 10.16 instead of 11.x.x - hasDynamicLoaderCache = major >= 11 || osVersion.equals("10.16"); - } + + // SDK 10.15 and earlier always reports 10.16 instead of 11.x.x + private static final boolean hasDynamicLoaderCache = OperatingSystem.version() + .compareTo(new Version(10, 16)) >= 0; private ClassLoaderHelper() {} diff --git a/src/java.base/share/classes/jdk/internal/util/OperatingSystem.java b/src/java.base/share/classes/jdk/internal/util/OperatingSystem.java index 3a44fcba0e5..f0ef93e0c4f 100644 --- a/src/java.base/share/classes/jdk/internal/util/OperatingSystem.java +++ b/src/java.base/share/classes/jdk/internal/util/OperatingSystem.java @@ -25,8 +25,6 @@ package jdk.internal.util; import jdk.internal.util.PlatformProps; import jdk.internal.vm.annotation.ForceInline; -import java.util.Locale; - /** * Enumeration of operating system types and testing for the current OS. * The enumeration can be used to dispatch to OS specific code or values. @@ -129,7 +127,34 @@ public enum OperatingSystem { * Names not recognized throw ExceptionInInitializerError with IllegalArgumentException. */ private static OperatingSystem initOS(String osName) { - return OperatingSystem.valueOf(osName.toUpperCase(Locale.ROOT)); + // Too early to use Locale conversions, manually do uppercase + StringBuilder sb = new StringBuilder(osName); + for (int i = 0; i < sb.length(); i++) { + char ch = sb.charAt(i); + if (ch >= 'a' && ch <= 'z') { + sb.setCharAt(i, (char)(ch - ('a' - 'A'))); // Map lower case down to uppercase + } + } + osName = sb.toString(); + return OperatingSystem.valueOf(osName); } + /** + * {@return the operating system version with major, minor, micro} + */ + public static Version version() { + return CURRENT_VERSION; + } + + // Parse and save the current version + private static final Version CURRENT_VERSION = initVersion(); + + private static Version initVersion() { + final String osVer = StaticProperty.osVersion(); + try { + return Version.parse(osVer); + } catch (IllegalArgumentException iae) { + throw new InternalError("os.version malformed: " + osVer, iae); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/util/StaticProperty.java b/src/java.base/share/classes/jdk/internal/util/StaticProperty.java index 92004802f84..3683aaeffcb 100644 --- a/src/java.base/share/classes/jdk/internal/util/StaticProperty.java +++ b/src/java.base/share/classes/jdk/internal/util/StaticProperty.java @@ -56,6 +56,7 @@ public final class StaticProperty { private static final String JAVA_LOCALE_USE_OLD_ISO_CODES; private static final String OS_NAME; private static final String OS_ARCH; + private static final String OS_VERSION; private StaticProperty() {} @@ -77,6 +78,7 @@ public final class StaticProperty { JAVA_LOCALE_USE_OLD_ISO_CODES = getProperty(props, "java.locale.useOldISOCodes", ""); OS_NAME = getProperty(props, "os.name"); OS_ARCH = getProperty(props, "os.arch"); + OS_VERSION = getProperty(props, "os.version"); } private static String getProperty(Properties props, String key) { @@ -265,4 +267,13 @@ public final class StaticProperty { public static String osArch() { return OS_ARCH; } + + /** + * {@return the {@code os.version} system property} + * {@link SecurityManager#checkPropertyAccess} is NOT checked + * in this method. This property is not considered security sensitive. + */ + public static String osVersion() { + return OS_VERSION; + } } diff --git a/src/java.base/share/classes/jdk/internal/util/Version.java b/src/java.base/share/classes/jdk/internal/util/Version.java new file mode 100644 index 00000000000..82f09cf8528 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/util/Version.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.util; + +/** + * A software Version with major, minor, and micro components. + * @param major major version + * @param minor minor version + * @param micro micro version + */ +public record Version(int major, int minor, int micro) implements Comparable { + + /** + * {@return a Version for major, minor versions} + * + * @param major major version + * @param minor minor version + */ + public Version(int major, int minor) { + this(major, minor, 0); + } + + /** + * {@return Compare this version with another version} + * + * @param other the object to be compared + */ + @Override + public int compareTo(Version other) { + int result = Integer.compare(major, other.major); + if (result == 0) { + result = Integer.compare(minor, other.minor); + if (result == 0) { + return Integer.compare(micro, other.micro); + } + } + return result; + } + + @Override + public String toString() { + return (micro == 0) + ? major + "." + minor + : major + "." + minor + "." + micro; + } + + /** + * {@return A Version parsed from a version string split on "." characters} + * Only major, minor, and micro version numbers are parsed, finer detail is ignored. + * Missing values for minor and micro are replaced with zero. + * The string must start with a number, if there is a '.' it must be followed by a number. + *

+ * Parsed by hand because it is called before RegEx can be initialized safely. + * + * @param str a version string + * @throws IllegalArgumentException if the string does not start with digits + * or digits do not follow '.' + */ + public static Version parse(String str) throws IllegalArgumentException { + int len = str.length(); + int majorStart = 0; + int majorEnd = skipDigits(str, majorStart); + int major = Integer.parseInt(str.substring(majorStart, majorEnd)); + + int minor = 0, micro = 0; + if (majorEnd < len && str.charAt(majorEnd) == '.') { + int minorStart = majorEnd + 1; + int minorEnd = skipDigits(str, minorStart); + minor = Integer.parseInt(str.substring(minorStart, minorEnd)); + + if (minorEnd < len && str.charAt(minorEnd) == '.') { + int microStart = minorEnd + 1; + int microEnd = skipDigits(str, microStart); + micro = Integer.parseInt(str.substring(microStart, microEnd)); + } + } + return new Version(major, minor, micro); + } + + /** + * {@return The index of the first non-digit from start} + * @throws IllegalArgumentException if there are no digits + */ + + private static int skipDigits(String s, int start) { + int index = start; + while (index < s.length() && Character.isDigit(s.charAt(index))) { + index++; + } + if (index == start) + throw new IllegalArgumentException("malformed version, missing digits: " + s); + return index; + } +} diff --git a/test/jdk/jdk/internal/util/OSTest.java b/test/jdk/jdk/internal/util/OSTest.java index 92a4781bb20..9bf28ce7c26 100644 --- a/test/jdk/jdk/internal/util/OSTest.java +++ b/test/jdk/jdk/internal/util/OSTest.java @@ -32,11 +32,14 @@ import static jdk.internal.util.OperatingSystem.LINUX; import static jdk.internal.util.OperatingSystem.MACOS; import static jdk.internal.util.OperatingSystem.WINDOWS; +import jdk.internal.util.StaticProperty; +import jdk.internal.util.Version; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /** @@ -83,4 +86,12 @@ public class OSTest { assertEquals(os == current, isXXX, "Mismatch " + os + " == " + current + " vs is" + os); } + + @Test + public void checkOsVersion() { + Version ver = OperatingSystem.version(); + String osVersion = StaticProperty.osVersion(); + System.err.printf("os.version: %s, version().toString(): %s%n", osVersion, ver); + assertTrue(osVersion.startsWith(ver.toString()), "version().toString() is not prefix of vs os.version property"); + } } diff --git a/test/jdk/jdk/internal/util/VersionTest.java b/test/jdk/jdk/internal/util/VersionTest.java new file mode 100644 index 00000000000..0b916a0463f --- /dev/null +++ b/test/jdk/jdk/internal/util/VersionTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import java.util.stream.Stream; + +import jdk.internal.util.Version; + +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @test + * @summary test jdk.internal.util.Version + * @modules java.base/jdk.internal.util + * @run junit VersionTest + */ + +public class VersionTest { + + private static Stream versionParams() { + return Stream.of( + Arguments.of("1", new Version(1, 0)), + Arguments.of("1.2", new Version(1, 2)), + Arguments.of("1.2", new Version(1, 2, 0)), + Arguments.of("1.2.3", new Version(1, 2, 3)), + Arguments.of("1-abc", new Version(1, 0, 0)), // Ignore extra + Arguments.of("1.2-abc", new Version(1, 2, 0)), // Ignore extra + Arguments.of("1.2.3.4", new Version(1, 2, 3)), // Ignore extra + Arguments.of("1.2.3-abc", new Version(1, 2, 3)) // Ignore extra + ); + } + + @ParameterizedTest + @MethodSource("versionParams") + public void checkParse(String verName, Version expected) { + Version actual = Version.parse(verName); + assertEquals(actual, expected, "Parsed version mismatch"); + } + + private static Stream illegalVersionParams() { + return Stream.of( + "1.", "1.2.", "1.-abc", "1.2.-abc", // dot without digits + "", // empty + "xaaa", "abc.xyz" // no initial digit + ); + } + + @ParameterizedTest() + @MethodSource("illegalVersionParams") + public void checkIllegalParse(String verName) { + Throwable th = assertThrows(IllegalArgumentException.class, () -> Version.parse(verName)); + String expectedMsg = "malformed version, missing digits: " + verName; + assertEquals(th.getMessage(), expectedMsg, "message mismatch"); + } + + private static Stream versionCompare() { + return Stream.of( + Arguments.of(new Version(2, 1), new Version(2, 1), 0), + Arguments.of(new Version(2, 1), new Version(2, 0), +1), + Arguments.of(new Version(2, 0), new Version(2, 1), -1), + Arguments.of(new Version(3, 3, 1), new Version(3, 3, 1), 0), + Arguments.of(new Version(3, 3, 1), new Version(3, 3, 0), +1), + Arguments.of(new Version(3, 3, 0), new Version(3, 3, 1), -1), + Arguments.of(new Version(2, 0), new Version(3, 0), -1), + Arguments.of(new Version(3, 0), new Version(2, 0), +1) + ); + } + + @ParameterizedTest() + @MethodSource("versionCompare") + public void checkVersionCompare(Version v1, Version v2, int expected) { + int result1 = v1.compareTo(v2); + assertEquals(result1, expected, "v1 vs v2"); + int result2 = v2.compareTo(v1); + assertEquals(result1, -result2, "compare not reflexive"); + } +}