/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 by Trifork * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package erjang; import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import erjang.beam.EUtil; // TODO: set proc.execption on catch to last known public abstract class ErlangException extends RuntimeException { public static final EAtom am_java_exception = EAtom .intern("java_exception"); static final EAtom am_error = EAtom.intern("error"); static final EAtom am_throw = EAtom.intern("throw"); static final EAtom am_exit = EAtom.intern("exit"); static final EAtom am_file = EAtom.intern("file"); static final EAtom am_line = EAtom.intern("line"); private EObject reason; public abstract EAtom getExClass(); public ErlangException(EObject reason) { this.reason = reason; //System.err.println(this.getClass().getName() + ": " + getMessage()); } public ErlangException(EObject reason, Throwable cause) { super(cause); this.reason = reason; //System.err.println(this.getClass().getName() + ": " + getMessage()); } public ErlangException(Throwable cause) { super(cause); this.reason = am_java_exception; //System.err.println(this.getClass().getName() + ": " + getMessage()); } public EObject reason() { return reason; } /* * (non-Javadoc) * * @see java.lang.Throwable#toString() */ @Override public String getMessage() { return String.valueOf(reason()); } /** * @return */ public EObject getCatchValue() { return new ExceptionTuple(this); } // @Override // public String toString() { // return (ETuple.make(ERT.am_EXIT, reason(), getTrace())).toString(); // } /** * */ private static final class ExceptionTuple extends ETuple { /** * */ private final ErlangException e; private ETuple instance; protected ETuple instantiate() { if (instance == null) { if (e instanceof ErlangThrow) { instance = new ETuple2(ERT.am_EXIT, e.reason); } else if (e instanceof ErlangExit) { instance = new ETuple2(ERT.am_EXIT, e.reason); } else { ETuple2 reason_plus_trace = new ETuple2(e.reason, e.getTrace()); instance = new ETuple2(ERT.am_EXIT, reason_plus_trace); } } return instance; } public int arity() {return instantiate().arity();} public EObject elm(int i) {return instantiate().elm(i);} public void set(int index, EObject term) {throw new IllegalStateException();} public ETuple blank() {return instantiate().blank();} /** * @param e */ private ExceptionTuple(ErlangException e) { this.e = e; } @Override public String toString() { return testTuple().toString(); } @Override int cmp_order() { return testTuple().cmp_order(); } @Override int compare_same(EObject rhs) { return testTuple().compare_same(rhs); } @Override public ETuple testTuple() { return instantiate(); } } // // the rest of this file is a big hack to reconstruct erlang traces... // static Map<StackTraceElement, ETuple4> cache = Collections .synchronizedMap(new WeakHashMap<StackTraceElement, ETuple4>()); public ESeq getTrace() { return decodeTrace(getStackTrace()); } public ESeq getLazyTrace() { return new ELazySeq() { @Override protected ESeq initialValue() { return getTrace(); } @Override public String toString() { return getTrace().toString(); } }; } public static ESeq decodeTrace(StackTraceElement[] st) { ESeq trace = ERT.NIL; for (int i = st.length - 1; i > 0; i--) { StackTraceElement st2 = st[i]; ETuple4 elem; if ((elem = cache.get(st2)) != null) { trace = trace.cons(elem); continue; } if ((elem = decodeTraceElem(st2)) != null) { trace = trace.cons(elem); } } return trace; } public static final String ERJANG_MODULES_DOT = "erjang.m."; /** * @param st * @return */ private static ETuple4 decodeTraceElem(StackTraceElement st) { String cname = st.getClassName(); String mname = st.getMethodName(); EAtom module = null; if (cname.startsWith(ERJANG_MODULES_DOT)) { int last = cname.lastIndexOf('.'); String moduleName = cname.substring(ERJANG_MODULES_DOT.length(), last); module = EAtom.intern(EUtil.decodeJavaName(moduleName)); } EAtom function = null; int arity = -1; { /* Does the method name end with /__([0-9]+)/? * If so, capture arity and function name. */ int i; char c; int w=1; int a=0; for (i = mname.length()-1; i>=2 && (c=mname.charAt(i)) >= '0' && c <= '9'; i--, w*=10) { a += w * (c-'0'); } if (w>1 && mname.charAt(i) == '_' && mname.charAt(i-1) == '_') { String method_name = mname.substring(0, i-1); function = EAtom.intern(EUtil.decodeJavaName(method_name)); arity = a; } } if (module != null && function != null && arity != -1) { ETuple4 res = new ETuple4(); res.elem1 = module; res.elem2 = function; res.elem3 = new ESmall(arity); res.elem4 = EList.make(new ETuple2(am_file, new EString(st.getFileName())), new ETuple2(am_line, ERT.box(st.getLineNumber()))); return res; } // Skip superfluous slow and failing class lookups: // OBS: Revisit if there ever are BIFs called 'go' or 'invoke'. if (mname.endsWith("$call") || mname.equals("go") || mname.equals("invoke")) return null; if (mname.equals("interpret") && cname.equals("erjang.beam.interpreter.Interpreter$Module$Function")) { ETuple4 res = new ETuple4(); res.elem1 = EAtom.intern("<interpreted>"); res.elem2 = EAtom.intern("<interpreted>"); res.elem3 = new ESmall(-1); res.elem4 = EList.make(new ETuple2(am_file, new EString("<unknown>")), new ETuple2(am_line, ERT.box(st.getLineNumber()))); return res; } Class<?> clazz = null; try { clazz = Class.forName(cname); } catch (ClassNotFoundException e) { return null; } Method m = find_method(clazz, mname); if (m == null) return null; BIF bif = m.getAnnotation(BIF.class); if (bif == null) return null; return resolve(clazz, m, bif); } /** * @param c * @param m * @param ann * @return */ private static ETuple4 resolve(Class<?> c, Method m, BIF ann1) { if (ann1 == null) return null; String module = get_class_module(c); String fun = null; if (ann1 != null && !("__SELFNAME__".equals(ann1.name()))) { fun = ann1.name(); } if (fun == null) { fun = m.getName(); } Class<?>[] parameterTypes = m.getParameterTypes(); int arity = parameterTypes.length; if (arity > 0) { if (parameterTypes[0].equals(EProc.class)) arity -= 1; } if (module != null && fun != null) { // erlang:apply/N doesn't show up in stack traces if ("erlang".equals(module) && "apply".equals(fun)) { return null; } ETuple4 res = new ETuple4(); res.elem1 = EAtom.intern(module); res.elem2 = EAtom.intern(fun); res.elem3 = ESmall.make(arity); res.elem4 = ERT.NIL; return res; } else { return null; } } /** * @param c * @return */ private static String get_class_module(Class<?> c) { Module m = c.getAnnotation(Module.class); if (m != null) { return m.value(); } else { String cname = c.getName(); if (cname.startsWith(ERJANG_MODULES_DOT)) { int last = cname.lastIndexOf('.'); return cname.substring(ERJANG_MODULES_DOT.length(), last); } } return null; } /** * @param c * @param mname * @return */ private static Method find_method(Class<?> c, String mname) { Method[] methods = c.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].getName().equals(mname)) return methods[i]; } return null; } /** * */ private static final EAtom UNKNOWN = EAtom.intern("unknown"); private EObject erl_value(Object val) { if (val instanceof EObject) return (EObject) val; if (val instanceof String) return EString.fromString((String) val); if (val instanceof Integer) return ESmall.make(((Integer) val).intValue()); throw new Error(); } /** * @return */ public ETuple3 getTryValue() { ETuple3 result = new ETuple3(); result.elem1 = getExClass(); result.elem2 = reason; result.elem3 = wrapAsObject(); return result; } public ExceptionAsObject wrapAsObject() { return new ExceptionAsObject(); } /** Exception wrapper to be used in 'try_case' and 'raise' instructions. */ public class ExceptionAsObject extends EPseudoTerm { @Override public ECons testNonEmptyList() { return ERT.NIL.cons ( ErlangException.this.getTrace() ); } public ErlangException getException() { return ErlangException.this; } @Override public int hashCode() { // Shouldn't be called. return System.identityHashCode(this); } @Override public String toString() { return "wrapped:" + ErlangException.this.toString(); } } }