RubyException.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
  15.  * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  16.  * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
  17.  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
  18.  * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
  19.  * Copyright (C) 2004 Joey Gibson <joey@joeygibson.com>
  20.  * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
  21.  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
  22.  * Copyright (C) 2005 David Corbin <dcorbin@users.sf.net>
  23.  * Copyright (C) 2006 Michael Studman <codehaus@michaelstudman.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  27.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the EPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the EPL, the GPL or the LGPL.
  36.  ***** END LICENSE BLOCK *****/
  37. package org.jruby;

  38. import org.jruby.anno.JRubyClass;
  39. import org.jruby.anno.JRubyMethod;
  40. import org.jruby.exceptions.JumpException.FlowControlException;
  41. import org.jruby.java.proxies.ConcreteJavaProxy;
  42. import org.jruby.runtime.*;
  43. import org.jruby.runtime.backtrace.BacktraceData;
  44. import org.jruby.runtime.backtrace.RubyStackTraceElement;
  45. import org.jruby.runtime.backtrace.TraceType;
  46. import org.jruby.runtime.builtin.IRubyObject;
  47. import org.jruby.runtime.builtin.Variable;
  48. import org.jruby.runtime.component.VariableEntry;
  49. import org.jruby.runtime.marshal.MarshalStream;
  50. import org.jruby.runtime.marshal.UnmarshalStream;

  51. import java.io.IOException;
  52. import java.io.PrintStream;
  53. import java.util.List;

  54. import static org.jruby.runtime.Visibility.PRIVATE;

  55. /**
  56.  *
  57.  * @author  jpetersen
  58.  */
  59. @JRubyClass(name="Exception")
  60. public class RubyException extends RubyObject {
  61.     protected RubyException(Ruby runtime, RubyClass rubyClass) {
  62.         this(runtime, rubyClass, null);
  63.     }

  64.     public RubyException(Ruby runtime, RubyClass rubyClass, String message) {
  65.         super(runtime, rubyClass);
  66.        
  67.         this.message = message == null ? runtime.getNil() : runtime.newString(message);
  68.     }

  69.     @JRubyMethod(optional = 2, visibility = PRIVATE)
  70.     public IRubyObject initialize(IRubyObject[] args, Block block) {
  71.         if (args.length == 1) message = args[0];

  72.         IRubyObject errinfo = getRuntime().getCurrentContext().getErrorInfo();
  73.         if (!errinfo.isNil()) cause = errinfo;
  74.         return this;
  75.     }

  76.     @JRubyMethod
  77.     public IRubyObject backtrace() {
  78.         IRubyObject bt = getBacktrace();
  79.         return bt;
  80.     }

  81.     @JRubyMethod(required = 1)
  82.     public IRubyObject set_backtrace(IRubyObject obj) {
  83.         if (obj.isNil()) {
  84.             backtrace = null;
  85.         } else if (isArrayOfStrings(obj)) {
  86.             backtrace = obj;
  87.         } else if (obj instanceof RubyString) {
  88.             backtrace = RubyArray.newArray(getRuntime(), obj);
  89.         } else {
  90.             throw getRuntime().newTypeError("backtrace must be Array of String or a single String");
  91.         }

  92.         return backtrace();
  93.     }
  94.    
  95.     @JRubyMethod(omit = true)
  96.     public IRubyObject backtrace_locations(ThreadContext context) {
  97.         Ruby runtime = context.runtime;
  98.         RubyStackTraceElement[] elements = backtraceData.getBacktrace(runtime);
  99.        
  100.         return RubyThread.Location.newLocationArray(runtime, elements);
  101.     }

  102.     @JRubyMethod(name = "exception", optional = 1, rest = true, meta = true)
  103.     public static IRubyObject exception(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
  104.         return ((RubyClass) recv).newInstance(context, args, block);
  105.     }

  106.     @JRubyMethod(optional = 1)
  107.     public RubyException exception(IRubyObject[] args) {
  108.         switch (args.length) {
  109.             case 0 :
  110.                 return this;
  111.             case 1 :
  112.                 if(args[0] == this) {
  113.                     return this;
  114.                 }
  115.                 RubyException ret = (RubyException)rbClone();
  116.                 ret.initialize(args, Block.NULL_BLOCK); // This looks wrong, but it's the way MRI does it.
  117.                 return ret;
  118.             default :
  119.                 throw getRuntime().newArgumentError("Wrong argument count");
  120.         }
  121.     }

  122.     public IRubyObject to_s(ThreadContext context) {
  123.         return to_s19(context);
  124.     }

  125.     @JRubyMethod(name = "to_s")
  126.     public IRubyObject to_s19(ThreadContext context) {
  127.         if (message.isNil()) return context.runtime.newString(getMetaClass().getRealClass().getName());

  128.         message.setTaint(isTaint());
  129.         return message.asString();
  130.     }

  131.     @JRubyMethod(name = "message")
  132.     public IRubyObject message(ThreadContext context) {
  133.         return callMethod(context, "to_s");
  134.     }

  135.     /** inspects an object and return a kind of debug information
  136.      *
  137.      *@return A RubyString containing the debug information.
  138.      */
  139.     @JRubyMethod(name = "inspect")
  140.     public IRubyObject inspect(ThreadContext context) {
  141.         // rb_class_name skips intermediate classes (JRUBY-6786)
  142.         RubyModule rubyClass = getMetaClass().getRealClass();
  143.         RubyString exception = RubyString.objAsString(context, this);

  144.         if (exception.isEmpty()) return getRuntime().newString(rubyClass.getName());
  145.         StringBuilder sb = new StringBuilder("#<");
  146.         sb.append(rubyClass.getName()).append(": ").append(exception.getByteList()).append(">");
  147.         return getRuntime().newString(sb.toString());
  148.     }

  149.     @JRubyMethod(name = "==")
  150.     @Override
  151.     public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
  152.         if (this == other) return context.runtime.getTrue();

  153.         boolean equal = context.runtime.getException().isInstance(other) &&
  154.                 callMethod(context, "message").equals(other.callMethod(context, "message")) &&
  155.                 callMethod(context, "backtrace").equals(other.callMethod(context, "backtrace")) &&
  156.                 getMetaClass().getRealClass() == other.getMetaClass().getRealClass();
  157.         return context.runtime.newBoolean(equal);
  158.     }

  159.     @JRubyMethod(name = "===", meta = true)
  160.     public static IRubyObject op_eqq(ThreadContext context, IRubyObject recv, IRubyObject other) {
  161.         Ruby runtime = context.runtime;
  162.         // special case non-FlowControlException Java exceptions so they'll be caught by rescue Exception
  163.         if (other instanceof ConcreteJavaProxy &&
  164.                 (recv == runtime.getException() || recv == runtime.getStandardError())) {

  165.             Object object = ((ConcreteJavaProxy)other).getObject();
  166.             if (object instanceof Throwable && !(object instanceof FlowControlException)) {
  167.                 if (recv == runtime.getException() || object instanceof Exception) {
  168.                     return context.runtime.getTrue();
  169.                 }
  170.             }
  171.         }
  172.         // fall back on default logic
  173.         return ((RubyClass)recv).op_eqq(context, other);
  174.     }

  175.     @JRubyMethod(name = "cause")
  176.     public IRubyObject cause(ThreadContext context) {
  177.         IRubyObject nil = context.nil;
  178.         if (cause != nil) return cause;
  179.         return nil;
  180.     }

  181.     public void setCause(IRubyObject cause) {
  182.         this.cause = cause;
  183.     }

  184.     public void setBacktraceData(BacktraceData backtraceData) {
  185.         this.backtraceData = backtraceData;
  186.     }

  187.     public BacktraceData getBacktraceData() {
  188.         return backtraceData;
  189.     }

  190.     public RubyStackTraceElement[] getBacktraceElements() {
  191.         if (backtraceData == null) {
  192.             return RubyStackTraceElement.EMPTY_ARRAY;
  193.         }
  194.         return backtraceData.getBacktrace(getRuntime());
  195.     }

  196.     public void prepareBacktrace(ThreadContext context, boolean nativeException) {
  197.         // if it's null, build a backtrace
  198.         if (backtraceData == null) {
  199.             backtraceData = context.runtime.getInstanceConfig().getTraceType().getBacktrace(context, nativeException);
  200.         }
  201.     }

  202.     /**
  203.      * Prepare an "integrated" backtrace that includes the normal Ruby trace plus non-filtered Java frames. Used by
  204.      * Java integration to show the Java frames for a JI-called method.
  205.      *
  206.      * @param context
  207.      * @param javaTrace
  208.      */
  209.     public void prepareIntegratedBacktrace(ThreadContext context, StackTraceElement[] javaTrace) {
  210.         // if it's null, build a backtrace
  211.         if (backtraceData == null) {
  212.             backtraceData = context.runtime.getInstanceConfig().getTraceType().getIntegratedBacktrace(context, javaTrace);
  213.         }
  214.     }

  215.     public void forceBacktrace(IRubyObject backtrace) {
  216.         backtraceData = BacktraceData.EMPTY;
  217.         set_backtrace(backtrace);
  218.     }
  219.    
  220.     public IRubyObject getBacktrace() {
  221.         if (backtrace == null) {
  222.             initBacktrace();
  223.         }
  224.         return backtrace;
  225.     }
  226.    
  227.     public void initBacktrace() {
  228.         Ruby runtime = getRuntime();
  229.         if (backtraceData == null) {
  230.             backtrace = runtime.getNil();
  231.         } else {
  232.             backtrace = TraceType.generateMRIBacktrace(runtime, backtraceData.getBacktrace(runtime));
  233.         }
  234.     }

  235.     @Override
  236.     public void copySpecialInstanceVariables(IRubyObject clone) {
  237.         RubyException exception = (RubyException)clone;
  238.         exception.backtraceData = backtraceData;
  239.         exception.backtrace = backtrace;
  240.         exception.message = message;
  241.     }

  242.     /**
  243.      * Print the Ruby exception's backtrace to the given PrintStream.
  244.      *
  245.      * @param errorStream the PrintStream to which backtrace should be printed
  246.      */
  247.     public void printBacktrace(PrintStream errorStream) {
  248.         printBacktrace(errorStream, 0);
  249.     }

  250.     /**
  251.      * Print the Ruby exception's backtrace to the given PrintStream. This
  252.      * version accepts a number of lines to skip and is primarily used
  253.      * internally for exception printing where the first line is treated specially.
  254.      *
  255.      * @param errorStream the PrintStream to which backtrace should be printed
  256.      */
  257.     public void printBacktrace(PrintStream errorStream, int skip) {
  258.         IRubyObject trace = callMethod(getRuntime().getCurrentContext(), "backtrace");
  259.         if (!trace.isNil() && trace instanceof RubyArray) {
  260.             IRubyObject[] elements = trace.convertToArray().toJavaArray();

  261.             for (int i = skip; i < elements.length; i++) {
  262.                 IRubyObject stackTraceLine = elements[i];
  263.                 if (stackTraceLine instanceof RubyString) {
  264.                     printStackTraceLine(errorStream, stackTraceLine);
  265.                 }
  266.             }
  267.         }
  268.     }

  269.     private void printStackTraceLine(PrintStream errorStream, IRubyObject stackTraceLine) {
  270.             errorStream.print("\tfrom " + stackTraceLine + '\n');
  271.     }
  272.    
  273.     private boolean isArrayOfStrings(IRubyObject backtrace) {
  274.         if (!(backtrace instanceof RubyArray)) return false;
  275.            
  276.         IRubyObject[] elements = ((RubyArray) backtrace).toJavaArray();
  277.        
  278.         for (int i = 0 ; i < elements.length ; i++) {
  279.             if (!(elements[i] instanceof RubyString)) return false;
  280.         }
  281.            
  282.         return true;
  283.     }

  284.     public static ObjectAllocator EXCEPTION_ALLOCATOR = new ObjectAllocator() {
  285.         @Override
  286.         public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  287.             RubyException instance = new RubyException(runtime, klass);

  288.             // for future compatibility as constructors move toward not accepting metaclass?
  289.             instance.setMetaClass(klass);

  290.             return instance;
  291.         }
  292.     };

  293.     private static final ObjectMarshal EXCEPTION_MARSHAL = new ObjectMarshal() {
  294.         @Override
  295.         public void marshalTo(Ruby runtime, Object obj, RubyClass type,
  296.                               MarshalStream marshalStream) throws IOException {
  297.             RubyException exc = (RubyException)obj;

  298.             marshalStream.registerLinkTarget(exc);
  299.             List<Variable<Object>> attrs = exc.getVariableList();
  300.             attrs.add(new VariableEntry<Object>(
  301.                     "mesg", exc.message == null ? runtime.getNil() : exc.message));
  302.             attrs.add(new VariableEntry<Object>("bt", exc.getBacktrace()));
  303.             marshalStream.dumpVariables(attrs);
  304.         }

  305.         @Override
  306.         public Object unmarshalFrom(Ruby runtime, RubyClass type,
  307.                                     UnmarshalStream unmarshalStream) throws IOException {
  308.             RubyException exc = (RubyException)type.allocate();

  309.             unmarshalStream.registerLinkTarget(exc);
  310.             // FIXME: Can't just pull these off the wire directly? Or maybe we should
  311.             // just use real vars all the time for these?
  312.             unmarshalStream.defaultVariablesUnmarshal(exc);

  313.             exc.message = (IRubyObject)exc.removeInternalVariable("mesg");
  314.             exc.set_backtrace((IRubyObject)exc.removeInternalVariable("bt"));

  315.             return exc;
  316.         }
  317.     };

  318.     public static RubyClass createExceptionClass(Ruby runtime) {
  319.         RubyClass exceptionClass = runtime.defineClass("Exception", runtime.getObject(), EXCEPTION_ALLOCATOR);
  320.         runtime.setException(exceptionClass);

  321.         exceptionClass.setClassIndex(ClassIndex.EXCEPTION);
  322.         exceptionClass.setReifiedClass(RubyException.class);

  323.         exceptionClass.setMarshal(EXCEPTION_MARSHAL);
  324.         exceptionClass.defineAnnotatedMethods(RubyException.class);

  325.         return exceptionClass;
  326.     }

  327.     public static RubyException newException(Ruby runtime, RubyClass excptnClass, String msg) {
  328.         return new RubyException(runtime, excptnClass, msg);
  329.     }

  330.     // rb_exc_new3
  331.     public static IRubyObject newException(ThreadContext context, RubyClass exceptionClass, IRubyObject message) {
  332.         return exceptionClass.callMethod(context, "new", message.convertToString());
  333.     }

  334.     private BacktraceData backtraceData;
  335.     private IRubyObject backtrace;
  336.     public IRubyObject message;
  337.     private IRubyObject cause = getRuntime().getNil();

  338.     public static final int TRACE_HEAD = 8;
  339.     public static final int TRACE_TAIL = 4;
  340.     public static final int TRACE_MAX = TRACE_HEAD + TRACE_TAIL + 6;

  341. }