/* * Copyright (c) 2007, 2012, 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 com.sun.max.vm.runtime; import static com.sun.max.vm.intrinsics.Infopoints.*; import static com.sun.max.vm.jdk.JDK_java_lang_Throwable.*; import static com.sun.max.vm.object.ArrayAccess.*; import static com.sun.max.vm.runtime.VMRegister.*; import com.sun.max.annotate.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.MaxineVM.Phase; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.jdk.*; import com.sun.max.vm.jdk.JDK_java_lang_Throwable.Backtrace; import com.sun.max.vm.object.*; import com.sun.max.vm.stack.*; import com.sun.max.vm.thread.*; import com.sun.max.vm.ti.*; import com.sun.max.vm.type.*; /** */ public final class Throw { private Throw() { } public static int TraceExceptions; public static boolean TraceExceptionsRaw; public static boolean FatalVMAssertions = true; public static boolean FatalOutOfMemory; private static int TraceExceptionsMaxFrames = 200; private static int TraceExceptionsRawMaxFrames = 200; private static String TraceExceptionsFilter; public static boolean ScanStackOnFatalError; static { VMOptions.addFieldOption("-XX:", "TraceExceptions", Throw.class, "Trace exception throwing: 0 = none, 1 = toString(), 2 = printStackTrace().", Phase.STARTING); VMOptions.addFieldOption("-XX:", "TraceExceptionsMaxFrames", Throw.class, "The max frames to dump for -XX:TraceExceptions=2.", Phase.STARTING); VMOptions.addFieldOption("-XX:", "TraceExceptionsRaw", Throw.class, "Report a stack frame dump for every exception thrown.", Phase.PRISTINE); VMOptions.addFieldOption("-XX:", "TraceExceptionsRawMaxFrames", Throw.class, "The max frames to dump for -XX:+TraceExceptionsRaw.", Phase.PRISTINE); VMOptions.addFieldOption("-XX:", "ScanStackOnFatalError", Throw.class, "Perform a raw stack scan when a fatal VM occurs.", Phase.PRISTINE); VMOptions.addFieldOption("-XX:", "FatalVMAssertions", Throw.class, "Convert assertions thrown in the VM code to fatal errors.", Phase.PRISTINE); VMOptions.addFieldOption("-XX:", "FatalOutOfMemory", Throw.class, "Quick exit of the VM on first OutOfMemoryError.", Phase.STARTING); } static class StackFrameDumper extends RawStackFrameVisitor { int frames; @Override public boolean visitFrame(StackFrameCursor current, StackFrameCursor callee) { if (frames++ < TraceExceptionsRawMaxFrames) { // N.B. use "->" to make dumped stacks look slightly different than exception stack traces. final Pointer ip = current.ipAsPointer(); Throw.logFrame(" -> ", current.targetMethod(), ip); } return true; } @Override public void done() { if (frames > TraceExceptionsRawMaxFrames) { Log.print(" ["); Log.print(frames); Log.println(" frames elided]"); } frames = 0; } } /** * Shared global object for dumping stack traces without incurring any allocation. */ private static final StackFrameDumper stackFrameDumper = new StackFrameDumper(); /** * Unwinds the current thread's stack to the frame containing an exception handler (there is guaranteed to be one). * * This method disables safepoints so that a GC request (or any other VM operation) cannot interrupt * the unwinding process. Why? A {@linkplain VmThread#unwindingStackFrameWalker(Throwable) shared} * stack walker object is used for unwinding and stack reference map preparation. * Safepoints are re-enabled when the exception object is {@linkplain VmThread#loadExceptionForHandler() loaded} * by the exception handler. * * @param throwable the object to be passed to the exception handler - must not be null * @param sp the stack pointer to be used when determining the point at which exception was raised * @param fp the frame pointer to be used when determining the point at which exception was raised * @param ip the instruction pointer to be used when determining the point at which exception was raised */ public static void raise(Throwable throwable, Pointer sp, Pointer fp, CodePointer ip) { VMTI.handler().raise(throwable, sp, fp, ip); convertAssertionToFatalError(throwable); FatalError.check(throwable != null, "Trying to raise an exception with a null Throwable object"); final VmStackFrameWalker sfw = VmThread.current().unwindingStackFrameWalker(throwable); VmThread.current().checkYellowZoneForRaisingException(); SafepointPoll.disable(); sfw.unwind(ip.toPointer(), sp, fp, throwable); FatalError.unexpected("could not find top-level exception handler"); } /** * Converts an {@link AssertionError} to a {@link FatalError} if {@link #FatalVMAssertions} is {@code true} * and {@code throwable} is an AssertionError. */ public static void convertAssertionToFatalError(Throwable throwable) { if (FatalVMAssertions && StackTraceInThrowable && throwable instanceof AssertionError) { Backtrace bt = JDK_java_lang_Throwable.getBacktrace(throwable); if (bt != null) { for (int i = 0; i < bt.count; i++) { ClassMethodActor cma = bt.methods[i]; if (cma.isInitializer() && AssertionError.class.isAssignableFrom(cma.holder().toJava())) { // still in exception constructor chain } else { // this is the method causing the assertion error if (cma.holder().classLoader == BootClassLoader.BOOT_CLASS_LOADER) { FatalError.unexpected("Assertion thrown in the VM", throwable); } break; } } } } if (FatalOutOfMemory && throwable instanceof OutOfMemoryError) { Log.print("Failing fast on "); Log.println(throwable); MaxineVM.native_exit(11); } } /** * Unwinds the current thread's stack to the frame containing an exception handler (there is guaranteed to be one). */ @INLINE public static void raise(Object throwable) { if (throwable == null || throwable instanceof Throwable) { raise(UnsafeCast.asThrowable(throwable)); } else { FatalError.unexpected("Object thrown not a throwable"); } } /** * Unwinds the current thread's stack to the frame containing an exception handler (there is guaranteed to be one). * * @param throwable the object to be passed to the exception handler. If this value is null, then a * {@link NullPointerException} is instantiated and raised instead. */ @INLINE public static void raise(Throwable throwable) { convertAndRaise(throwable, getCpuStackPointer(), getCpuFramePointer(), CodePointer.from(here())); } @NEVER_INLINE public static void convertAndRaise(Throwable throwable, Pointer sp, Pointer fp, CodePointer ip) { if (throwable == null) { throwable = new NullPointerException(); } convertAssertionToFatalError(throwable); traceThrow(throwable); raise(throwable, sp, fp, ip); } public static void traceThrow(Throwable throwable) { if (TraceExceptions == 1) { Log.printThread(VmThread.current(), false); Log.println(": Throwing " + throwable); } else if (TraceExceptions >= 2) { StackTraceElement[] trace = throwable.getStackTrace(); Log.printThread(VmThread.current(), false); Log.print(": Throwing "); Log.println(throwable); for (int i = 0; i < trace.length && i < TraceExceptionsMaxFrames; i++) { Log.println("\tat " + trace[i]); } int elided = trace.length - TraceExceptionsMaxFrames; if (elided > 0) { Log.print("\t["); Log.print(elided); Log.println(" frames elided]"); } } if (TraceExceptionsRaw) { stackDumpWithException(throwable); } } public static void stackDumpWithException(Object throwable) { stackDump("Throwing " + throwable + ";", Pointer.fromLong(here()), getCpuStackPointer(), getCpuFramePointer()); } /** * Dumps the physical frames of a given stack to the {@link Log} stream. * * @param message if not {@code null}, this message is printed on a separate line prior to the stack trace * @param ip the instruction pointer at which to begin the stack trace * @param sp the stack pointer at which to begin the stack trace * @param fp the frame pointer at which to begin the stack trace */ @NEVER_INLINE public static void stackDump(String message, Pointer ip, final Pointer sp, final Pointer fp) { if (message != null) { Log.println(message); } VmThread.current().stackDumpStackFrameWalker().inspect(ip, sp, fp, stackFrameDumper); } /** * Dumps the stack of the thread denoted by a given VM thread locals pointer. * * @param message if not {@code null}, this message is printed on a separate line prior to the stack trace * @param tla * * @see #stackDump(String, Pointer, Pointer, Pointer) */ @NEVER_INLINE public static void stackDump(String message, final Pointer tla) { if (message != null) { Log.println(message); } Pointer anchor = JavaFrameAnchor.from(tla); Pointer ip = anchor.isZero() ? Pointer.zero() : JavaFrameAnchor.PC.get(anchor); final VmThread vmThread = VmThread.fromTLA(tla); if (ip.isZero()) { Log.print("Cannot dump stack for non-stopped thread "); Log.printThread(vmThread, true); } else { final Pointer sp = JavaFrameAnchor.SP.get(anchor); final Pointer fp = JavaFrameAnchor.FP.get(anchor); stackDump(null, ip, sp, fp); } } /** * Dumps the stack of the current thread. * * @param message if not {@code null}, this message is printed on a separate line prior to the stack trace * * @see #stackDump(String, Pointer, Pointer, Pointer) */ @NEVER_INLINE public static void stackDump(String message) { stackDump(message, Pointer.fromLong(here()), getCpuStackPointer(), getCpuFramePointer()); } /** * Scans the stack between the specified addresses in search of potential code pointers. * <p> * This stack scanning is even more primitive than a stack dump, since it does not rely on * the stack frame walker to walk individual frames, but instead scans the memory sequentially, * inspecting each word in the memory range to see if it is a potential code pointer. * * @param message the message to print for this stack scan * @param sp the pointer to top of the stack * @param end the pointer to the end of the stack */ public static void stackScan(String message, final Pointer sp, final Pointer end) { Log.println(message); Pointer pointer = sp.wordAligned(); while (pointer.lessThan(end)) { final CodePointer potentialCodePointer = CodePointer.from(pointer.getWord()); final TargetMethod targetMethod = potentialCodePointer.toTargetMethod(); if (targetMethod != null) { logFrame(null, targetMethod, potentialCodePointer.toPointer()); } pointer = pointer.plus(Word.size()); } } /** * Raises an {@code ArrayIndexOutOfBoundsException}. This is out-of-line to reduce the amount * of code inlined on the fast path for an array bounds check. * * @param array the array being accessed * @param index the index that is out of the bounds of {@code array} */ @NEVER_INLINE public static ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException(Object array, int index) { FatalError.check(array != null, "Arguments for raising an ArrayIndexOutOfBoundsException cannot be null"); throw new ArrayIndexOutOfBoundsException("Index: " + index + ", Array length: " + readArrayLength(array)); } /** * Raises an {@code ArrayStoreException}. This is out-of-line to reduce the amount * of code inlined on the fast path for an array store check. * * @param array the array being accessed * @param value the value whose type is not assignable to the component type of {@code array} */ @NEVER_INLINE public static ArrayStoreException arrayStoreException(Object array, Object value) { FatalError.check(array != null && value != null, "Arguments for raising an ArrayStoreException cannot be null"); final ClassActor arrayClassActor = MaxineVM.isHosted() ? ClassActor.fromJava(array.getClass()) : ObjectAccess.readClassActor(array); final ClassActor componentClassActor = arrayClassActor.componentClassActor(); throw new ArrayStoreException(value.getClass().getName() + " is not assignable to " + componentClassActor.name); } /** * Raises an {@code ClassCastException}. This is out-of-line to reduce the amount * of code inlined on the fast path for a type check. * * @param classActor the type being checked against * @param object the object whose type is not assignable to {@code classActor} */ @NEVER_INLINE public static ClassCastException classCastException(ClassActor classActor, Object object) { FatalError.check(object != null && classActor != null, "Arguments for raising a ClassCastException cannot be null"); throw new ClassCastException(object.getClass().getName() + " is not assignable to " + classActor.name); } @NEVER_INLINE public static NullPointerException nullPointerException() { throw new NullPointerException(); } /** * Raises an {@code NegativeArraySizeException}. This is out-of-line to reduce the amount * of code inlined on the fast path for an array allocation. * * @param length the negative array length */ @NEVER_INLINE public static NegativeArraySizeException negativeArraySizeException(int length) { throw new NegativeArraySizeException(String.valueOf(length)); } /** * Prints a line to the log stream for a given frame. */ public static void logFrame(String prefix, TargetMethod targetMethod, Pointer ip) { if (prefix != null) { Log.print(prefix); } if (targetMethod != null) { Log.printMethod(targetMethod, false); CodePointer codeStart = targetMethod.codeStart(); Log.print(" ["); Log.print(codeStart); Log.print("+"); Log.print(ip.minus(codeStart.toAddress()).toInt()); Log.print("]"); } else { Log.print("native{"); Log.printSymbol(ip); Log.print("}"); } Log.println(); } }