From 8b83de8985c0a645316b9767a2417f072473191a Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 19 Aug 2014 11:08:15 -0600 Subject: [PATCH] add avian.Machine.tryNative This function allows you to call native code such that any SIGSEGV/SIGBUS/SIGFPE/EXC_ACCESS_VIOLATION/etc. raised by that code is transformed into a Java exception and thrown by tryNative. Note that this effectively results in a longjmp out of whatever function raised the exception, so any C++ destructors or other cleanup code will not be run. --- classpath/avian/Machine.java | 29 ++++++++++++++++++++++++ src/avian/machine.h | 1 + src/builtin.cpp | 14 ++++++++++++ src/compile.cpp | 43 ++++++++++++++++++++++++++---------- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/classpath/avian/Machine.java b/classpath/avian/Machine.java index b219e91293..cdd7bf55cc 100644 --- a/classpath/avian/Machine.java +++ b/classpath/avian/Machine.java @@ -35,4 +35,33 @@ public abstract class Machine { return unsafe; } + /** + * Short version: Don't use this function -- it's evil. + * + * Long version: This is kind of a poor man's, cross-platform + * version of Microsoft's Structured Exception Handling. The idea + * is that you can call a native function with the specified + * argument such that any OS signals raised (e.g. SIGSEGV, SIGBUS, + * SIGFPE, EXC_ACCESS_VIOLATION, etc.) prior to the function + * returning are converted into the appropriate Java exception + * (e.g. NullPointerException, ArithmeticException, etc.) and thrown + * from this method. This may be useful in very specific + * circumstances, e.g. to work around a bug in a library that would + * otherwise crash your app. On the other hand, you'd be much + * better off just fixing the library if possible. + * + * Caveats: The specified function should return quickly without + * blocking, since it will block critical VM features such as + * garbage collection. The implementation is equivalent to using + * setjmp/longjmp to achieve a non-local return from the signal + * handler, meaning C++ destructors and other cleanup code might not + * be run if a signal is raised. It might melt your keyboard and + * burn your fingertips. Caveat lector. + * + * @param function a function pointer of type int64_t (*)(int64_t) + * @param argument the argument to pass to the function + * @return the return value of the function + */ + public static native long tryNative(long function, long argument); + } diff --git a/src/avian/machine.h b/src/avian/machine.h index 36f65ee868..f13aabf96b 100644 --- a/src/avian/machine.h +++ b/src/avian/machine.h @@ -1119,6 +1119,7 @@ class Thread { static const unsigned ActiveFlag = 1 << 5; static const unsigned SystemFlag = 1 << 6; static const unsigned JoinFlag = 1 << 7; + static const unsigned TryNativeFlag = 1 << 8; class Protector { public: diff --git a/src/builtin.cpp b/src/builtin.cpp index c9423d75d2..d479c70b2c 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -309,6 +309,20 @@ extern "C" AVIAN_EXPORT void JNICALL #endif // AVIAN_HEAPDUMP +extern "C" AVIAN_EXPORT int64_t JNICALL + Avian_avian_Machine_tryNative(Thread* t, object, uintptr_t* arguments) +{ + int64_t function; + memcpy(&function, arguments, 8); + int64_t argument; + memcpy(&argument, arguments + 2, 8); + + t->flags |= Thread::TryNativeFlag; + THREAD_RESOURCE0(t, t->flags &= ~Thread::TryNativeFlag); + + return reinterpret_cast(function)(argument); +} + extern "C" AVIAN_EXPORT void JNICALL Avian_java_lang_Runtime_exit(Thread* t, object, uintptr_t* arguments) { diff --git a/src/compile.cpp b/src/compile.cpp index 6435df5bf3..cbe4b1e4f1 100644 --- a/src/compile.cpp +++ b/src/compile.cpp @@ -8164,6 +8164,18 @@ class SignalHandler : public SignalRegistrar::Handler { { } + void setException(MyThread* t) { + if (ensure(t, pad(fixedSize) + traceSize(t))) { + atomicOr(&(t->flags), Thread::TracingFlag); + t->exception = makeThrowable(t, type); + atomicAnd(&(t->flags), ~Thread::TracingFlag); + } else { + // not enough memory available for a new exception and stack + // trace -- use a preallocated instance instead + t->exception = (vm::roots(t)->*exc)(); + } + } + virtual bool handleSignal(void** ip, void** frame, void** stack, @@ -8171,8 +8183,23 @@ class SignalHandler : public SignalRegistrar::Handler { { MyThread* t = static_cast(m->localThread->get()); if (t and t->state == Thread::ActiveState) { - GcMethod* node = methodForIp(t, *ip); - if (node) { + if (t->flags & Thread::TryNativeFlag) { + setException(t); + + popResources(t); + + GcContinuation* continuation; + findUnwindTarget(t, ip, frame, stack, &continuation); + + t->trace->targetMethod = 0; + t->trace->nativeMethod = 0; + + transition(t, *ip, *stack, continuation, t->trace); + + *thread = t; + + return true; + } else if (methodForIp(t, *ip)) { // add one to the IP since findLineNumber will subtract one // when we make the trace: MyThread::TraceContext context( @@ -8182,22 +8209,14 @@ class SignalHandler : public SignalRegistrar::Handler { t->continuation, t->trace); - if (ensure(t, pad(fixedSize) + traceSize(t))) { - atomicOr(&(t->flags), Thread::TracingFlag); - t->exception = makeThrowable(t, type); - atomicAnd(&(t->flags), ~Thread::TracingFlag); - } else { - // not enough memory available for a new exception and stack - // trace -- use a preallocated instance instead - t->exception = (vm::roots(t)->*exc)(); - } + setException(t); // printTrace(t, t->exception); GcContinuation* continuation; findUnwindTarget(t, ip, frame, stack, &continuation); - transition(t, ip, stack, continuation, t->trace); + transition(t, *ip, *stack, continuation, t->trace); *thread = t;