Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8267989: Exceptions thrown during upcalls should be handled #543

Closed
@@ -2352,6 +2352,11 @@ public boolean isEnableNativeAccess(Module m) {
public long findNative(ClassLoader loader, String entry) {
return ClassLoader.findNative(loader, entry);
}

@Override
public void exit(int statusCode) {
Shutdown.exit(statusCode);
}
});
}
}
@@ -384,4 +384,10 @@ public interface JavaLangAccess {
boolean isEnableNativeAccess(Module m);

long findNative(ClassLoader loader, String entry);

/**
* Direct access to Shutdown.exit to avoid security manager checks
* @param statusCode the status code
*/
void exit(int statusCode);
}
@@ -25,8 +25,6 @@
*/
package jdk.incubator.foreign;

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.PlatformLayouts;
import jdk.internal.foreign.SystemLookup;
@@ -39,7 +37,6 @@
import java.lang.invoke.MethodType;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

import static jdk.internal.foreign.PlatformLayouts.*;
@@ -224,9 +221,13 @@ static SymbolLookup systemLookup() {
* Allocates a native stub with given scope which can be passed to other foreign functions (as a function pointer);
* calling such a function pointer from native code will result in the execution of the provided method handle.
*
* <p>The returned memory address is associated with the provided scope. When such scope is closed,
* <p>
* The returned memory address is associated with the provided scope. When such scope is closed,
* the corresponding native stub will be deallocated.
* <p>
* The target method handle should not throw any exceptions. If the target method handle does throw an exception,
* the VM will exit with a non-zero exit code.
* <p>
* This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
@@ -25,9 +25,10 @@
*/
package jdk.internal.foreign;

import jdk.internal.foreign.abi.SharedUtils;
import sun.security.action.GetPropertyAction;

import static jdk.incubator.foreign.MemoryLayouts.ADDRESS;
import static sun.security.action.GetPropertyAction.privilegedGetProperty;

public enum CABI {
SysV,
@@ -37,8 +38,8 @@ public enum CABI {
private static final CABI current;

static {
String arch = System.getProperty("os.arch");
String os = System.getProperty("os.name");
String arch = privilegedGetProperty("os.arch");
String os = privilegedGetProperty("os.name");
long addressSize = ADDRESS.bitSize();
// might be running in a 32-bit VM on a 64-bit platform.
// addressSize will be correctly 32
@@ -295,6 +295,9 @@ private static Object invokeInterpBindings(Object[] moves, MethodHandle leaf,
} else {
return returnMoves;
}
} catch(Throwable t) {
SharedUtils.handleUncaughtException(t);
JornVernee marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
}

@@ -37,6 +37,8 @@
import jdk.incubator.foreign.SequenceLayout;
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.ValueLayout;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.CABI;
import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.Utils;
@@ -72,13 +74,16 @@

public class SharedUtils {

private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();

private static final MethodHandle MH_ALLOC_BUFFER;
private static final MethodHandle MH_BASEADDRESS;
private static final MethodHandle MH_BUFFER_COPY;
private static final MethodHandle MH_MAKE_CONTEXT_NO_ALLOCATOR;
private static final MethodHandle MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR;
private static final MethodHandle MH_CLOSE_CONTEXT;
private static final MethodHandle MH_REACHBILITY_FENCE;
private static final MethodHandle MH_HANDLE_UNCAUGHT_EXCEPTION;

static {
try {
@@ -97,6 +102,8 @@ public class SharedUtils {
methodType(void.class));
MH_REACHBILITY_FENCE = lookup.findStatic(Reference.class, "reachabilityFence",
methodType(void.class, Object.class));
MH_HANDLE_UNCAUGHT_EXCEPTION = lookup.findStatic(SharedUtils.class, "handleUncaughtException",
methodType(void.class, Throwable.class));
} catch (ReflectiveOperationException e) {
throw new BootstrapMethodError(e);
}
@@ -359,14 +366,25 @@ private static MethodHandle reachabilityFenceHandle(Class<?> type) {
return MH_REACHBILITY_FENCE.asType(MethodType.methodType(void.class, type));
}

static void handleUncaughtException(Throwable t) {
if (t != null) {
t.printStackTrace();
JLA.exit(1);
}
}

static MethodHandle wrapWithAllocator(MethodHandle specializedHandle,
int allocatorPos, long bufferCopySize,
boolean upcall) {
// insert try-finally to close the NativeScope used for Binding.Copy
MethodHandle closer;
int insertPos;
if (specializedHandle.type().returnType() == void.class) {
closer = empty(methodType(void.class, Throwable.class)); // (Throwable) -> void
if (!upcall) {
closer = empty(methodType(void.class, Throwable.class)); // (Throwable) -> void
JornVernee marked this conversation as resolved.
Show resolved Hide resolved
} else {
closer = MH_HANDLE_UNCAUGHT_EXCEPTION;
}
insertPos = 1;
} else {
closer = identity(specializedHandle.type().returnType()); // (V) -> V
@@ -28,7 +28,7 @@
* @modules jdk.incubator.foreign/jdk.internal.foreign
* @build NativeTestHelper CallGeneratorHelper TestUpcall
*
* @run testng/othervm
* @run testng/othervm/timeout=720
JornVernee marked this conversation as resolved.
Show resolved Hide resolved
* --enable-native-access=ALL-UNNAMED
* TestUpcall
*/
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2021, 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
* @requires ((os.arch == "amd64" | os.arch == "x86_64") & sun.arch.data.model == "64") | os.arch == "aarch64"
* @library /test/lib
* @modules jdk.incubator.foreign/jdk.internal.foreign
* @build ThrowingUpcall TestUpcallException
*
* @run testng/othervm/native
* --enable-native-access=ALL-UNNAMED
* TestUpcallException
*/

import jdk.test.lib.Utils;
import org.testng.annotations.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.List;

import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;

public class TestUpcallException {
JornVernee marked this conversation as resolved.
Show resolved Hide resolved

@Test
public void testExceptionInterpreted() throws InterruptedException, IOException {
boolean useSpec = false;
run(useSpec);
}

@Test
public void testExceptionSpecialized() throws IOException, InterruptedException {
boolean useSpec = true;
run(useSpec);
}

private void run(boolean useSpec) throws IOException, InterruptedException {
Process process = new ProcessBuilder()
.command(
Paths.get(Utils.TEST_JDK)
.resolve("bin")
.resolve("java")
.toAbsolutePath()
.toString(),
"--add-modules", "jdk.incubator.foreign",
"--enable-native-access=ALL-UNNAMED",
"-Djava.library.path=" + System.getProperty("java.library.path"),
"-Djdk.internal.foreign.ProgrammableUpcallHandler.USE_SPEC=" + useSpec,
"-cp", Utils.TEST_CLASS_PATH,
// security manager to block normal System.exit
"-Djava.security.manager=allow",
"ThrowingUpcall")
.start();

int result = process.waitFor();
assertNotEquals(result, 0);

List<String> outLines = linesFromStream(process.getInputStream());
outLines.forEach(System.out::println);
List<String> errLines = linesFromStream(process.getErrorStream());
errLines.forEach(System.err::println);

// Exception message would be found in stack trace
String shouldInclude = "Testing upcall exceptions";
assertTrue(linesContain(errLines, shouldInclude), "Did not find '" + shouldInclude + "' in stderr");

// If the VM crashes with an uncaught IllegalStateException from the security manager
// the crash log should include the exception message.
// Make sure that is _not_ the case.
String shouldNotInclude = "Can not use exitVM";
assertFalse(linesContain(outLines, shouldNotInclude), "Found '" + shouldNotInclude + "' in stdout");
}

private boolean linesContain(List<String> errLines, String shouldInclude) {
return errLines.stream().anyMatch(line -> line.contains(shouldInclude));
}

private static List<String> linesFromStream(InputStream stream) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
return reader.lines().toList();
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2021, 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 jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SymbolLookup;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.security.Permission;

import static jdk.incubator.foreign.CLinker.C_POINTER;

public class ThrowingUpcall {

private static final MethodHandle downcall;
private static final MethodHandle MH_throwException;

static {
System.setSecurityManager(new SecurityManager() {
@Override
public void checkExit(int status) {
throw new IllegalStateException("Can not use exitVM");
}

@Override
public void checkPermission(Permission perm) {
// do nothing
}
});

System.loadLibrary("TestUpcall");
SymbolLookup lookup = SymbolLookup.loaderLookup();
downcall = CLinker.getInstance().downcallHandle(
lookup.lookup("f0_V__").orElseThrow(),
MethodType.methodType(void.class, MemoryAddress.class),
FunctionDescriptor.ofVoid(C_POINTER)
);

try {
MH_throwException = MethodHandles.lookup().findStatic(ThrowingUpcall.class, "throwException",
MethodType.methodType(void.class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}

public static void throwException() throws Throwable {
throw new Throwable("Testing upcall exceptions");
}

public static void main(String[] args) throws Throwable {
test();
}

public static void test() throws Throwable {
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
MemoryAddress stub = CLinker.getInstance().upcallStub(MH_throwException, FunctionDescriptor.ofVoid(), scope);

downcall.invokeExact(stub); // should call System.exit(1);
}
}

}