8331670: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

Reviewed-by: mcimadamore, jpai, pminborg
This commit is contained in:
Alan Bateman 2024-05-28 15:05:54 +00:00
parent 51ae08f72b
commit 0f3e2cc334
8 changed files with 1229 additions and 84 deletions

View File

@ -94,7 +94,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \
TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \
SMALL_JAVA := false, \
CLASSPATH := $(JMH_COMPILE_JARS), \
DISABLED_WARNINGS := restricted this-escape processing rawtypes cast \
DISABLED_WARNINGS := restricted this-escape processing rawtypes removal cast \
serial preview dangling-doc-comments, \
SRC := $(MICROBENCHMARK_SRC), \
BIN := $(MICROBENCHMARK_CLASSES), \

View File

@ -2282,6 +2282,15 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m
if (res != JNI_OK) {
return res;
}
} else if (match_option(option, "--sun-misc-unsafe-memory-access=", &tail)) {
if (strcmp(tail, "allow") == 0 || strcmp(tail, "warn") == 0 || strcmp(tail, "debug") == 0 || strcmp(tail, "deny") == 0) {
PropertyList_unique_add(&_system_properties, "sun.misc.unsafe.memory.access", tail,
AddProperty, WriteableProperty, InternalProperty);
} else {
jio_fprintf(defaultStream::error_stream(),
"Value specified to --sun-misc-unsafe-memory-access not recognized: '%s'\n", tail);
return JNI_ERR;
}
} else if (match_option(option, "--illegal-access=", &tail)) {
char version[256];
JDK_Version::jdk(17).to_string(version, sizeof(version));

View File

@ -212,7 +212,11 @@ java.launcher.X.usage=\n\
\ --finalization=<value>\n\
\ controls whether the JVM performs finalization of objects,\n\
\ where <value> is one of "enabled" or "disabled".\n\
\ Finalization is enabled by default.\n\n\
\ Finalization is enabled by default.\n\
\ --sun-misc-unsafe-memory-access=<value>\n\
\ allow or deny usage of unsupported API sun.misc.Unsafe\n\
\ <value> is one of "allow", "warn", "debug", or "deny".\n\
\ The default value is "allow".\n\n\
These extra options are subject to change without notice.\n
# Translators please note do not translate the options themselves

View File

@ -1194,6 +1194,30 @@ or directories.
.TP
\f[V]--source\f[R] \f[I]version\f[R]
Sets the version of the source in source-file mode.
.TP
\f[V]--sun-misc-unsafe-memory-acces=\f[R] \f[I]value\f[R]
Allow or deny usage of unsupported API \f[V]sun.misc.Unsafe\f[R].
\f[I]value\f[R] is one of:
.RS
.TP
\f[V]allow\f[R]
Allow use of the memory-access methods with no warnings at run time.
.TP
\f[V]warn\f[R]
Allow use of the memory-access methods, but issues a warning on the
first occasion that any memory-access method is used.
At most one warning is issued.
.TP
\f[V]debug\f[R]
Allow use of the memory-access methods, but issue a one-line warning and
a stack trace when any memory-access method is used.
.TP
\f[V]deny\f[R]
Disallow use of the memory-access methods by throwing an
\f[V]UnsupportedOperationException\f[R] on every usage.
.PP
The default value when the option is not specified is \f[V]allow\f[R].
.RE
.SH EXTRA OPTIONS FOR MACOS
.PP
The following extra options are macOS specific.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2024, 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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import sun.misc.Unsafe;
/**
* Launched by UnsafeMemoryAccessWarnings with a '+' delimited list of methods to invoke.
*/
@SuppressWarnings("removal")
public class TryUnsafeMemoryAccess {
private static final Unsafe UNSAFE;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
UNSAFE = (Unsafe) f.get(null);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
private static long address;
private static long offset;
static class TestClass {
long value;
TestClass(long value) {
this.value = value;
}
}
/**
* The argument is a list of names of no-arg static methods in this class to invoke.
* The names are separated with a '+'.
*/
public static void main(String[] args) throws Exception {
String[] methodNames = args[0].split("\\+");
for (String methodName : methodNames) {
Method m = TryUnsafeMemoryAccess.class.getDeclaredMethod(methodName);
try {
m.invoke(null);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
// a selection of Unsafe memory access methods to test
static void allocateMemory() {
address = UNSAFE.allocateMemory(100);
}
static void freeMemory() {
if (address == 0)
throw new RuntimeException("allocateMemory not called");
UNSAFE.freeMemory(address);
}
static void objectFieldOffset() throws Exception {
Field f = TestClass.class.getDeclaredField("value");
offset = UNSAFE.objectFieldOffset(f);
}
static void getLong() {
if (offset == 0)
throw new RuntimeException("objectFieldOffset not called");
var obj = new TestClass(99);
long value = UNSAFE.getLong(obj, offset);
if (value != 99) {
throw new RuntimeException();
}
}
static void putLong() {
if (offset == 0)
throw new RuntimeException("objectFieldOffset not called");
var obj = new TestClass(0);
UNSAFE.putLong(obj, offset, 99);
if (obj.value != 99) {
throw new RuntimeException();
}
}
static void invokeCleaner() {
var dbb = ByteBuffer.allocateDirect(1000);
UNSAFE.invokeCleaner(dbb);
}
/**
* Invoke Unsafe.allocateMemory reflectively.
*/
static void reflectivelyAllocateMemory() throws Exception {
Method allocateMemory = Unsafe.class.getMethod("allocateMemory", long.class);
address = (long) allocateMemory.invoke(UNSAFE, 100);
}
/**
* Invoke Unsafe.freeMemory reflectively.
*/
static void reflectivelyFreeMemory() throws Exception {
if (address == 0)
throw new RuntimeException("allocateMemory not called");
Method freeMemory = Unsafe.class.getMethod("freeMemory", long.class);
freeMemory.invoke(UNSAFE, address);
}
/**
* Used to test that the property value from startup is used.
*/
static void setSystemPropertyToAllow() {
System.setProperty("sun.misc.unsafe.memory.access", "allow");
}
}

View File

@ -0,0 +1,212 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8331670
* @summary Basic test for --sun-misc-unsafe-memory-access=<value>
* @library /test/lib
* @compile TryUnsafeMemoryAccess.java
* @run junit UnsafeMemoryAccessWarnings
*/
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
class UnsafeMemoryAccessWarnings {
/**
* Test default is "allow"
*/
@Test
void testDefault() throws Exception {
test("allocateMemory+freeMemory+objectFieldOffset+putLong+getLong+invokeCleaner")
.shouldHaveExitValue(0)
.shouldNotContain("WARNING: A terminally deprecated method in sun.misc.Unsafe has been called")
.shouldNotContain("WARNING: sun.misc.Unsafe::allocateMemory")
.shouldNotContain("WARNING: sun.misc.Unsafe::freeMemory")
.shouldNotContain("WARNING: sun.misc.Unsafe::objectFieldOffset")
.shouldNotContain("WARNING: sun.misc.Unsafe::putLong")
.shouldNotContain("WARNING: sun.misc.Unsafe::getLong")
.shouldNotContain("WARNING: sun.misc.Unsafe::invokeCleaner");
}
/**
* Test --sun-misc-unsafe-memory-access=allow
*/
@Test
void testAllow() throws Exception {
test("allocateMemory+freeMemory+objectFieldOffset+putLong+getLong+invokeCleaner",
"--sun-misc-unsafe-memory-access=allow")
.shouldHaveExitValue(0)
.shouldNotContain("WARNING: A terminally deprecated method in sun.misc.Unsafe has been called")
.shouldNotContain("WARNING: sun.misc.Unsafe::allocateMemory")
.shouldNotContain("WARNING: sun.misc.Unsafe::freeMemory")
.shouldNotContain("WARNING: sun.misc.Unsafe::objectFieldOffset")
.shouldNotContain("WARNING: sun.misc.Unsafe::putLong")
.shouldNotContain("WARNING: sun.misc.Unsafe::getLong")
.shouldNotContain("WARNING: sun.misc.Unsafe::invokeCleaner");
}
/**
* Test --sun-misc-unsafe-memory-access=warn
*/
@ParameterizedTest
@ValueSource(strings = {
"allocateMemory+freeMemory",
"objectFieldOffset+putLong+getLong",
"invokeCleaner"
})
void testWarn(String input) throws Exception {
var output = test(input, "--sun-misc-unsafe-memory-access=warn").shouldHaveExitValue(0);
// should be warning printed for the first memory access method
String[] methodNames = input.split("\\+");
String firstMethodName = methodNames[0];
output.shouldContain("WARNING: A terminally deprecated method in sun.misc.Unsafe has been called")
.shouldContain("WARNING: sun.misc.Unsafe::" + firstMethodName + " has been called by")
.shouldContain("WARNING: Please consider reporting this to the maintainers of")
.shouldContain("WARNING: sun.misc.Unsafe::" + firstMethodName + " will be removed in a future release");
// should be no warning for the second/subsequent memory access methods
int index = 1;
while (index < methodNames.length) {
String methodName = methodNames[index++];
output.shouldNotContain("WARNING: sun.misc.Unsafe::" + methodName);
}
}
/**
* Test --sun-misc-unsafe-memory-access=debug
*/
@Test
void testDebug() throws Exception {
test("allocateMemory+freeMemory+objectFieldOffset+putLong+getLong+invokeCleaner",
"--sun-misc-unsafe-memory-access=debug")
.shouldHaveExitValue(0)
.shouldContain("WARNING: sun.misc.Unsafe::allocateMemory called")
.shouldContain("WARNING: sun.misc.Unsafe::freeMemory called")
.shouldContain("WARNING: sun.misc.Unsafe::objectFieldOffset called")
.shouldContain("WARNING: sun.misc.Unsafe::putLong called")
.shouldContain("WARNING: sun.misc.Unsafe::getLong called")
.shouldContain("WARNING: sun.misc.Unsafe::invokeCleaner called");
}
/**
* Test --sun-misc-unsafe-memory-access=deny
*/
@Test
void testDeny() throws Exception {
test("allocateMemory+objectFieldOffset+invokeCleaner", "--sun-misc-unsafe-memory-access=deny")
.shouldHaveExitValue(0)
.shouldContain("java.lang.UnsupportedOperationException: allocateMemory")
.shouldContain("java.lang.UnsupportedOperationException: objectFieldOffset")
.shouldContain("java.lang.UnsupportedOperationException: invokeCleaner");
}
/**
* Test invoking Unsafe methods with core reflection.
*/
@Test
void testInvokeReflectively() throws Exception {
test("reflectivelyAllocateMemory+reflectivelyFreeMemory", "--sun-misc-unsafe-memory-access=allow")
.shouldHaveExitValue(0)
.shouldNotContain("WARNING: A terminally deprecated method in sun.misc.Unsafe has been called")
.shouldNotContain("WARNING: sun.misc.Unsafe::allocateMemory")
.shouldNotContain("WARNING: sun.misc.Unsafe::freeMemory");
test("reflectivelyAllocateMemory+reflectivelyFreeMemory", "--sun-misc-unsafe-memory-access=warn")
.shouldHaveExitValue(0)
.shouldContain("WARNING: A terminally deprecated method in sun.misc.Unsafe has been called")
.shouldContain("WARNING: sun.misc.Unsafe::allocateMemory has been called by")
.shouldContain("WARNING: Please consider reporting this to the maintainers of")
.shouldContain("WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release")
.shouldNotContain("WARNING: sun.misc.Unsafe::freeMemory");
test("reflectivelyAllocateMemory+reflectivelyFreeMemory", "--sun-misc-unsafe-memory-access=debug")
.shouldHaveExitValue(0)
.shouldContain("WARNING: sun.misc.Unsafe::allocateMemory called")
.shouldContain("WARNING: sun.misc.Unsafe::freeMemory called");
test("reflectivelyAllocateMemory", "--sun-misc-unsafe-memory-access=deny")
.shouldHaveExitValue(0)
.shouldContain("java.lang.UnsupportedOperationException: allocateMemory");
}
/**
* If --sun-misc-unsafe-memory-access specified more than once then last one wins.
*/
@Test
void testLastOneWins() throws Exception {
test("allocateMemory+objectFieldOffset+invokeCleaner",
"--sun-misc-unsafe-memory-access=allow",
"--sun-misc-unsafe-memory-access=deny")
.shouldHaveExitValue(0)
.shouldContain("java.lang.UnsupportedOperationException: allocateMemory")
.shouldContain("java.lang.UnsupportedOperationException: objectFieldOffset")
.shouldContain("java.lang.UnsupportedOperationException: invokeCleaner");
}
/**
* Test --sun-misc-unsafe-memory-access with invalid values.
*/
@ParameterizedTest
@ValueSource(strings = { "", "bad" })
void testInvalidValues(String value) throws Exception {
test("allocateMemory", "--sun-misc-unsafe-memory-access=" + value)
.shouldNotHaveExitValue(0)
.shouldContain("Value specified to --sun-misc-unsafe-memory-access not recognized: '" + value);
}
/**
* Test System.setProperty("sun.misc.unsafe.memory.access", "allow")
* The saved value from startup should be used, not the system property set at run-time.
*/
@Test
void testSetPropertyToAllow() throws Exception {
test("setSystemPropertyToAllow+objectFieldOffset", "--sun-misc-unsafe-memory-access=deny")
.shouldHaveExitValue(0)
.shouldContain("java.lang.UnsupportedOperationException: objectFieldOffset");
}
/**
* Launch TryUnsafeMemoryAccess with the given arguments and VM options.
*/
private OutputAnalyzer test(String action, String... vmopts) throws Exception {
Stream<String> s1 = Stream.of(vmopts);
Stream<String> s2 = Stream.of("TryUnsafeMemoryAccess", action);
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
var outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.err)
.errorTo(System.err);
return outputAnalyzer;
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2024, 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 org.openjdk.bench.sun.misc;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import sun.misc.Unsafe;
import org.openjdk.jmh.annotations.*;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(org.openjdk.jmh.annotations.Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@SuppressWarnings("removal")
public class UnsafeOps {
static final Unsafe U;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
U = (Unsafe) f.get(null);
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException();
}
}
private static class TestClass {
long value;
}
private Object object;
private long valueOffset;
private long address;
@Setup
public void setup() throws Exception {
object = new TestClass();
Field f = TestClass.class.getDeclaredField("value");
valueOffset = U.objectFieldOffset(f);
address = U.allocateMemory(1000);
}
@TearDown
public void finish() {
U.freeMemory(address);
}
@Benchmark
public void putLongOnHeap() {
U.putLong(object, 0, 99);
}
@Benchmark
public long getLongOnHeap() {
return U.getLong(object, 0);
}
@Benchmark
public void putLongOffHeap() {
U.putLong(null, address, 99);
}
@Benchmark
public long getLongOffHeap() {
return U.getLong(null, address);
}
}