diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java index f490f7cfc28..eb1f5376e1a 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java @@ -21,7 +21,6 @@ import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JBlock; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; -import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JIfStatement; import com.google.gwt.dev.jjs.ast.JInstanceOf; @@ -31,7 +30,6 @@ import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; -import com.google.gwt.dev.jjs.ast.JNewInstance; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; @@ -143,26 +141,10 @@ public boolean visit(JMethodBody x, Context ctx) { } private class UnwrapJavaScriptExceptionVisitor extends JModVisitor { - JDeclaredType jseType = - program.getFromTypeMap("com.google.gwt.core.client.JavaScriptException"); JMethod unwrapMethod = program.getIndexedMethod(RuntimeConstants.EXCEPTIONS_UNWRAP); @Override public void endVisit(JThrowStatement x, Context ctx) { - assert jseType != null; - - JExpression expr = x.getExpr(); - - // Optimization: unwrap not needed if "new BlahException()" - if (expr instanceof JNewInstance && !expr.getType().equals(jseType)) { - return; - } - - // Optimization: unwrap not needed if expression can never be JavaScriptException - if (program.typeOracle.castFailsTrivially((JReferenceType) expr.getType(), jseType)) { - return; - } - // throw x; -> throw Exceptions.unwrap(x); ctx.replaceMe(createUnwrappedThrow(x)); } diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java index cac61a54076..487047b28fb 100644 --- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java +++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java @@ -27,42 +27,28 @@ final class Exceptions { @DoNotInline // This frame can be useful in understanding the native stack static Object wrap(Object e) { + // Although this is impossible to happen in code generated from Java (as we always unwrap + // before throwing), there are code out there where the Java exception is instantiated and + // thrown in native code, hence we may receive it already wrapped. if (e instanceof Throwable) { return e; } - JavaScriptException jse = getCachedJavaScriptException(e); - if (jse == null) { - jse = new JavaScriptException(e); - StackTraceCreator.captureStackTrace(jse, e); - cacheJavaScriptException(e, jse); + Throwable javaException = getJavaException(e); + if (javaException == null) { + javaException = new JavaScriptException(e); + StackTraceCreator.captureStackTrace(javaException); } - - return jse; + return javaException; } - static Object unwrap(Object e) { - if (e instanceof JavaScriptException) { - JavaScriptException jse = ((JavaScriptException) e); - if (jse.isThrownSet()) { - return jse.getThrown(); - } - } - return e; - } - - private static native JavaScriptException getCachedJavaScriptException(Object e) /*-{ - return e && e.__gwt$exception; + @DoNotInline // This method shouldn't be inlined and pruned as JsStackEmulator needs it. + static native Object unwrap(Object t)/*-{ + return t.@Throwable::backingJsObject; }-*/; - private static native void cacheJavaScriptException(Object e, JavaScriptException jse) /*-{ - if (e && typeof e == 'object') { - try { - e.__gwt$exception = jse; - } catch (ignored) { - // See https://code.google.com/p/google-web-toolkit/issues/detail?id=8449 - } - } + private static native Throwable getJavaException(Object e)/*-{ + return e && e["__java$exception"]; }-*/; static AssertionError makeAssertionError() { diff --git a/dev/core/super/javaemul/internal/ConsoleLogger.java b/dev/core/super/javaemul/internal/ConsoleLogger.java index 0974c377705..43cc11912f2 100644 --- a/dev/core/super/javaemul/internal/ConsoleLogger.java +++ b/dev/core/super/javaemul/internal/ConsoleLogger.java @@ -68,7 +68,7 @@ function stringify(fnStack) { } return "\t" + fnStack.join("\n\t"); } - var backingError = t.__gwt$backingJsError; - return backingError && (backingError.stack || stringify(backingError.fnStack)); + var backingError = t.backingJsObject; + return backingError && (backingError.stack || stringify(t.fnStack)); }-*/; } diff --git a/dev/core/super/javaemul/internal/JsUtils.java b/dev/core/super/javaemul/internal/JsUtils.java index b219be7e303..31063024373 100644 --- a/dev/core/super/javaemul/internal/JsUtils.java +++ b/dev/core/super/javaemul/internal/JsUtils.java @@ -37,6 +37,10 @@ public static native String unsafeCastToString(Object string) /*-{ return string; }-*/; + public static native void setProperty(Object map, String key, Object value) /*-{ + map[key] = value; + }-*/; + public static native int getIntProperty(Object map, String key) /*-{ return map[key]; }-*/; diff --git a/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java b/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java index 5ee2152c267..de2ad5d3683 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java @@ -146,7 +146,7 @@ public void testSimpleThrow() throws Exception { checkOnModuleLoad(program, "function onModuleLoad(){" + "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + "$location[stackIndex]='EntryPoint.java:'+'3',$clinit_EntryPoint();" + - "throw $location[stackIndex]='EntryPoint.java:'+'4',new RuntimeException" + + "throw unwrap(($location[stackIndex]='EntryPoint.java:'+'4',new RuntimeException))" + "}"); } @@ -170,9 +170,9 @@ public void testThrowWithInlineMethodCall() throws Exception { checkOnModuleLoad(program, "function onModuleLoad(){" + "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + "$location[stackIndex]='EntryPoint.java:'+'6',$clinit_EntryPoint();" + - "throw new RuntimeException(" + + "throw unwrap(new RuntimeException(" + "($tmp=($location[stackIndex]='EntryPoint.java:'+'4',thing).toString()," + - "$location[stackIndex]='EntryPoint.java:'+'7',$tmp))" + + "$location[stackIndex]='EntryPoint.java:'+'7',$tmp)))" + "}"); } @@ -226,7 +226,7 @@ public void testTryCatch() throws Exception { checkOnModuleLoad(program, "function onModuleLoad(){" + "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + "$location[stackIndex]='EntryPoint.java:'+'3',$clinit_EntryPoint();var e,s;" + - "try{throw $location[stackIndex]='EntryPoint.java:'+'5',new RuntimeException" + + "try{throw unwrap(($location[stackIndex]='EntryPoint.java:'+'5',new RuntimeException))" + "}catch($e0){$e0=wrap($e0);" + "$stackDepth=($location[stackIndex]='EntryPoint.java:'+'6',stackIndex);" + "if(instanceOf($e0,'java.lang.RuntimeException')){" + diff --git a/tools/api-checker/config/gwt27_28userApi.conf b/tools/api-checker/config/gwt27_28userApi.conf index 1f1704b4fec..f0a5f78bcc8 100644 --- a/tools/api-checker/config/gwt27_28userApi.conf +++ b/tools/api-checker/config/gwt27_28userApi.conf @@ -91,6 +91,7 @@ excludedFiles_new **/linker/**\ :**/server/**\ :**/tools/**\ :**/vm/**\ +:user/src/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java\ :user/src/com/google/gwt/core/client/impl/WeakMapping.java\ :user/src/com/google/gwt/core/shared/impl/ThrowableTypeResolver.java\ :user/src/com/google/gwt/i18n/**/impl/cldr/**\ diff --git a/user/BUILD b/user/BUILD index b504ef22e0c..c2612235fcc 100644 --- a/user/BUILD +++ b/user/BUILD @@ -310,6 +310,7 @@ java_library( "src/com/google/gwt/user/client/impl/DomImpl.java", "src/com/google/gwt/user/client/HistoryListener.java", "src/com/google/gwt/user/client/Cookies.java", + "src/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java", "src/com/google/gwt/user/client/ui/AcceptsOneWidget.java", "src/com/google/gwt/user/client/ui/Widget.java", "src/com/google/gwt/user/client/ui/IsWidget.java", diff --git a/user/src/com/google/gwt/core/client/JavaScriptException.java b/user/src/com/google/gwt/core/client/JavaScriptException.java index 139554189c0..ef63d65ae1e 100644 --- a/user/src/com/google/gwt/core/client/JavaScriptException.java +++ b/user/src/com/google/gwt/core/client/JavaScriptException.java @@ -15,6 +15,8 @@ */ package com.google.gwt.core.client; +import com.google.gwt.core.client.impl.JavaScriptExceptionBase; + /** * Any JavaScript exceptions occurring within JSNI methods are wrapped as this * class when caught in Java code. The wrapping does not occur until the @@ -30,7 +32,7 @@ * determined, {@link #fillInStackTrace()} can be called in the associated catch * block to create a stack trace corresponding to the location where the * JavaScriptException object was created. - * + * *
  * try {
  *   nativeMethod();
@@ -41,7 +43,7 @@
  * }
  * 
*/ -public final class JavaScriptException extends RuntimeException { +public final class JavaScriptException extends JavaScriptExceptionBase { private static final Object NOT_SET = new Object(); @@ -108,14 +110,13 @@ public JavaScriptException(Object e) { * trace */ public JavaScriptException(Object e, String description) { - // Stack trace is writeable for outside just for classic devmode but otherwise it is not and we - // don't want unnecessary fillInStackTrace calls from super constructor as well. - super(null, null, true, !GWT.isScript()); + super(e); this.e = e; this.description = description; } public JavaScriptException(String name, String description) { + super(null); this.message = "JavaScript " + name + " exception: " + description; this.name = name; this.description = description; @@ -128,9 +129,10 @@ public JavaScriptException(String name, String description) { * @param message the detail message */ protected JavaScriptException(String message) { - super(message); + super(null); this.message = this.description = message; this.e = NOT_SET; + fillInStackTrace(); } /** diff --git a/user/src/com/google/gwt/core/client/impl/Impl.java b/user/src/com/google/gwt/core/client/impl/Impl.java index 0626b198d43..ac6354cc45d 100644 --- a/user/src/com/google/gwt/core/client/impl/Impl.java +++ b/user/src/com/google/gwt/core/client/impl/Impl.java @@ -27,6 +27,12 @@ */ public final class Impl { + static { + if (GWT.isClient() && StackTraceCreator.collector != null) { + // Just enforces loading of StackTraceCreator early on, nothing else to do here... + } + } + private static final int WATCHDOG_ENTRY_DEPTH_CHECK_INTERVAL_MS = 2000; /** diff --git a/user/src/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java b/user/src/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java new file mode 100644 index 00000000000..a9748da7794 --- /dev/null +++ b/user/src/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Google Inc. + * + * 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 com.google.gwt.core.client.impl; + +/** + * A helper so that we can change the parent of JavaScriptException via super-source. Otherwise we + * would need to copy whole content of the class and also dev mode would choke. + */ +public class JavaScriptExceptionBase extends RuntimeException { + public JavaScriptExceptionBase(Object e) { + super(); + } +} \ No newline at end of file diff --git a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java index 640ffc9b475..78a85629df4 100644 --- a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java +++ b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java @@ -56,7 +56,7 @@ public class StackTraceCreator { */ abstract static class Collector { - public abstract void collect(Object t, Object jsThrown); + public abstract void collect(Object error); public abstract StackTraceElement[] getStackTrace(Object t); } @@ -68,10 +68,10 @@ abstract static class Collector { static class CollectorLegacy extends Collector { @Override - public native void collect(Object t, Object thrownIgnored) /*-{ + public native void collect(Object error) /*-{ var seen = {}; var fnStack = []; - t.__gwt$backingJsError = { fnStack: fnStack }; + error.fnStack= fnStack; // Ignore the collect() call var callee = arguments.callee.caller; @@ -117,9 +117,9 @@ public StackTraceElement[] getStackTrace(Object t) { static final class CollectorEmulated extends Collector { @Override - public native void collect(Object t, Object jsThrownIgnored) /*-{ + public native void collect(Object error) /*-{ var fnStack = []; - t.__gwt$backingJsError = { fnStack: fnStack }; + error.fnStack= fnStack; for (var i = 0; i < $stackDepth; i++) { var location = $location[i]; var fn = $stack[i]; @@ -162,44 +162,10 @@ public StackTraceElement[] getStackTrace(Object t) { */ static class CollectorModern extends Collector { - static { - increaseStackTraceLimit(); - } - - // As of today, only available in IE10+ and Chrome. - private static native void increaseStackTraceLimit() /*-{ - // TODO(cromwellian) make this a configurable? - Error.stackTraceLimit = 64; - }-*/; - @Override - public native void collect(Object t, Object jsThrown) /*-{ - // Carefully crafted to delay the 'stack' property until stack trace construction as it is - // costly in some browsers (e.g. Chrome). - - function fixIE(e) { - // In IE -unlike every other browser-, the stack property is not defined until you throw - // the Error object. Sometimes I hope they would just stop developing browsers... - if (!("stack" in e)) { - try { throw e; } catch(ignored) {} - } - return e; - } - - var backingJsError; - if (typeof jsThrown == 'string') { - // Replace newlines with spaces so that we don't confuse the parser - // below which splits on newlines, and will otherwise try to parse - // the error message as part of the stack trace. - backingJsError = fixIE(new Error(jsThrown.replace('\n', ' '))); - } else if (jsThrown && typeof jsThrown == 'object' && "stack" in jsThrown){ - backingJsError = jsThrown; - } else { - backingJsError = fixIE(new Error()); - } - - t.__gwt$backingJsError = backingJsError; - }-*/; + public void collect(Object error) { + // No op, already collected by the error itself. + } @Override public StackTraceElement[] getStackTrace(Object t) { @@ -326,7 +292,7 @@ private static native int parseInt(String number) /*-{ */ static class CollectorNull extends Collector { @Override - public void collect(Object ignored, Object jsThrownIgnored) { + public void collect(Object error) { // Nothing to do } @@ -339,8 +305,8 @@ public StackTraceElement[] getStackTrace(Object ignored) { /** * Collect necessary information to construct stack trace trace later in time. */ - public static void captureStackTrace(Throwable throwable, Object reference) { - collector.collect(throwable, reference); + public static void captureStackTrace(Object error) { + collector.collect(error); } public static StackTraceElement[] constructJavaStackTrace(Throwable thrown) { @@ -351,10 +317,13 @@ public static StackTraceElement[] constructJavaStackTrace(Throwable thrown) { private static StackTraceElement[] dropInternalFrames(StackTraceElement[] stackTrace) { final String dropFrameUntilFnName = Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceCreator::captureStackTrace(*)"); + final String dropFrameUntilFnName2 = + Impl.getNameOf("@java.lang.Throwable::initializeBackingError(*)"); - int numberOfFrameToSearch = Math.min(stackTrace.length, DROP_FRAME_LIMIT); - for (int i = 0; i < numberOfFrameToSearch; i++) { - if (stackTrace[i].getMethodName().equals(dropFrameUntilFnName)) { + int numberOfFramesToSearch = Math.min(stackTrace.length, DROP_FRAME_LIMIT); + for (int i = numberOfFramesToSearch - 1; i >= 0; i--) { + if (stackTrace[i].getMethodName().equals(dropFrameUntilFnName) + || stackTrace[i].getMethodName().equals(dropFrameUntilFnName2)) { splice(stackTrace, i + 1); break; } @@ -381,11 +350,15 @@ private static void splice(Object[] arr, int length) { private static native boolean supportsErrorStack() /*-{ // Error.stackTraceLimit is cheaper to check and available in both IE and Chrome - return !!Error.stackTraceLimit || "stack" in new Error(); + if (Error.stackTraceLimit > 0) { + Error.stackTraceLimit = 64; + return true; + } + + return "stack" in new Error(); }-*/; - private static native JsArrayString getFnStack(Object t) /*-{ - var e = t.__gwt$backingJsError; + private static native JsArrayString getFnStack(Object e) /*-{ return (e && e.fnStack) ? e.fnStack : []; }-*/; @@ -401,7 +374,7 @@ static native String extractFunctionName(String fnName) /*-{ }-*/; private static native JsArrayString split(Object t) /*-{ - var e = t.__gwt$backingJsError; + var e = t.backingJsObject; return (e && e.stack) ? e.stack.split('\n') : []; }-*/; } diff --git a/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java b/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java new file mode 100644 index 00000000000..4177b7944bc --- /dev/null +++ b/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Google Inc. + * + * 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 com.google.gwt.core.client.impl; + +import com.google.gwt.core.client.GwtScriptOnly; + +/** + * Super-source of JavaScriptExceptionBase that extends from JsException. + */ +@GwtScriptOnly +public class JavaScriptExceptionBase extends JsException { + protected JavaScriptExceptionBase(Object e) { + super(e); + } +} diff --git a/user/super/com/google/gwt/emul/java/lang/Exception.java b/user/super/com/google/gwt/emul/java/lang/Exception.java index b04c4fb9521..e886a5fd66d 100644 --- a/user/super/com/google/gwt/emul/java/lang/Exception.java +++ b/user/super/com/google/gwt/emul/java/lang/Exception.java @@ -41,4 +41,8 @@ protected Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } + + Exception(Object backingJsObject) { + super(backingJsObject); + } } diff --git a/user/super/com/google/gwt/emul/java/lang/JsException.java b/user/super/com/google/gwt/emul/java/lang/JsException.java new file mode 100644 index 00000000000..47b73546cca --- /dev/null +++ b/user/super/com/google/gwt/emul/java/lang/JsException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Google Inc. + * + * 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 java.lang; + +/** + * Abstracts an object thrown natively in JavaScript. Thrown objects are most of the time a + * JavaScript Error but not guaranteed to be as JavaScript code can throw anything including + * primitives like {@code null}, numbers, etc. + */ +public class JsException extends RuntimeException { + protected JsException(Object backingJsObject) { + super(backingJsObject); + } +} + diff --git a/user/super/com/google/gwt/emul/java/lang/RuntimeException.java b/user/super/com/google/gwt/emul/java/lang/RuntimeException.java index 6a359e83d7a..33a5473732c 100644 --- a/user/super/com/google/gwt/emul/java/lang/RuntimeException.java +++ b/user/super/com/google/gwt/emul/java/lang/RuntimeException.java @@ -41,4 +41,8 @@ protected RuntimeException(String message, Throwable cause, boolean enableSuppre boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } + + RuntimeException(Object backingJsObject) { + super(backingJsObject); + } } diff --git a/user/super/com/google/gwt/emul/java/lang/Throwable.java b/user/super/com/google/gwt/emul/java/lang/Throwable.java index d39e45ffc05..2d074ecdd05 100644 --- a/user/super/com/google/gwt/emul/java/lang/Throwable.java +++ b/user/super/com/google/gwt/emul/java/lang/Throwable.java @@ -22,12 +22,19 @@ import java.io.PrintStream; import java.io.Serializable; +import javaemul.internal.JsUtils; +import javaemul.internal.annotations.DoNotInline; +import jsinterop.annotations.JsProperty; + /** * See the * official Java API doc for details. */ public class Throwable implements Serializable { + + private static final Object UNITIALIZED = new Object(); + /* * NOTE: We cannot use custom field serializers because we need the client and * server to use different serialization strategies to deal with this type. @@ -39,35 +46,40 @@ public class Throwable implements Serializable { * to ensure that only the detailMessage field is serialized. Changing the * field modifiers below may necessitate a change to the server's * SerializabilityUtil.fieldQualifiesForSerialization(Field) method. - * - * TODO(rluble): Add remaining functionality for suppressed Exceptions (e.g. - * printing). Also review the class for missing Java 7 compatibility. */ - private transient Throwable cause; private String detailMessage; + private transient Throwable cause; private transient Throwable[] suppressedExceptions; private transient StackTraceElement[] stackTrace; private transient boolean disableSuppression; + private transient boolean writetableStackTrace = true; + + @JsProperty + private transient Object backingJsObject = UNITIALIZED; public Throwable() { fillInStackTrace(); + initializeBackingError(); } public Throwable(String message) { this.detailMessage = message; fillInStackTrace(); + initializeBackingError(); } public Throwable(String message, Throwable cause) { this.cause = cause; this.detailMessage = message; fillInStackTrace(); + initializeBackingError(); } public Throwable(Throwable cause) { this.detailMessage = (cause == null) ? null : cause.toString(); this.cause = cause; fillInStackTrace(); + initializeBackingError(); } /** @@ -78,10 +90,58 @@ protected Throwable(String message, Throwable cause, boolean enableSuppression, boolean writetableStackTrace) { this.cause = cause; this.detailMessage = message; + this.writetableStackTrace = writetableStackTrace; this.disableSuppression = !enableSuppression; if (writetableStackTrace) { fillInStackTrace(); } + initializeBackingError(); + } + + Throwable(Object backingJsObject) { + setBackingJsObject(backingJsObject); + } + + private void initializeBackingError() { + this.stackTrace = null; // Invalidate the cached trace + + // Replace newlines with spaces so that we don't confuse the parser + // below which splits on newlines, and will otherwise try to parse + // the error message as part of the stack trace. + // TODO: use string.asNativeString.replace instead when available. + String errorMessage = internalToString().replace('\n', ' '); + + setBackingJsObject(createError(errorMessage)); + + captureStackTrace(); + } + + // TODO(goktug): set 'name' property to class name and 'message' to detailMessage instead when + // they are respected by dev tools logging. + private static native Object createError(String msg) /*-{ + var e = new Error(msg); + + // In IE -unlike every other browser-, the stack property is not defined until you throw it. + if (!("stack" in e)) { + try { throw e; } catch(ignored) {} + } + + return e; + }-*/; + + private native void captureStackTrace() /*-{ + @com.google.gwt.core.client.impl.StackTraceCreator::captureStackTrace(*)(this); + }-*/; + + private void setBackingJsObject(Object backingJsObject) { + this.backingJsObject = backingJsObject; + linkBack(backingJsObject); + } + + private void linkBack(Object error) { + if (error != null) { + JsUtils.setProperty(error, "__java$exception", this); + } } /** @@ -109,11 +169,18 @@ public final void addSuppressed(Throwable exception) { * * @return this */ - public native Throwable fillInStackTrace() /*-{ - this.@Throwable::stackTrace = null; // Invalidate the cached trace - @com.google.gwt.core.client.impl.StackTraceCreator::captureStackTrace(*)(this, this.@Throwable::detailMessage); + @DoNotInline + public Throwable fillInStackTrace() { + if (writetableStackTrace) { + // If this is the first run, let constructor initialize it. + // (We need to initialize the backingJsObject from constructor as our own implementation of + // fillInStackTrace is not guaranteed to be executed.) + if (backingJsObject != UNITIALIZED) { + initializeBackingError(); + } + } return this; - }-*/; + } public Throwable getCause() { return cause; @@ -134,13 +201,13 @@ public String getMessage() { */ public StackTraceElement[] getStackTrace() { if (stackTrace == null) { - stackTrace = constructJavaStackTrace(this); + stackTrace = constructJavaStackTrace(); } return stackTrace; } - private static native StackTraceElement[] constructJavaStackTrace(Throwable t) /*-{ - return @com.google.gwt.core.client.impl.StackTraceCreator::constructJavaStackTrace(*)(t); + private native StackTraceElement[] constructJavaStackTrace() /*-{ + return @com.google.gwt.core.client.impl.StackTraceCreator::constructJavaStackTrace(*)(this); }-*/; /** @@ -200,13 +267,12 @@ public void setStackTrace(StackTraceElement[] stackTrace) { @Override public String toString() { - String className = this.getClass().getName(); - String msg = getMessage(); - if (msg != null) { - return className + ": " + msg; - } else { - return className; - } + return internalToString(); } + // A private method to avoid polymorphic calls from constructor. + private String internalToString() { + String className = getClass().getName(); + return detailMessage == null ? className : className + ": " + detailMessage; + } } diff --git a/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java b/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java index 9faa5e61b3d..0763f647e1a 100644 --- a/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java +++ b/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java @@ -156,12 +156,22 @@ public void testCatch() { assertJavaScriptException(jso, catchJava(createThrowRunnable(e))); } - public void testCatchNativePropagatedFromFinally() { - RuntimeException e = new RuntimeException(); - assertSame(e, catchNative(wrapWithFinally(createThrowRunnable(e)))); + @DoNotRunWith(Platform.Devel) + public void testCatchNative() { + RuntimeException e = new RuntimeException(""); + Object caughtNative = catchNative(createThrowRunnable(e)); + assertTrue(caughtNative instanceof JavaScriptObject); + assertTrue(caughtNative.toString().contains("")); + assertTrue(caughtNative.toString().contains(RuntimeException.class.getName())); JavaScriptObject jso = makeJSO(); e = new JavaScriptException(jso); + assertSame(jso, catchNative(createThrowRunnable(e))); + } + + public void testCatchNativePropagatedFromFinally() { + JavaScriptObject jso = makeJSO(); + JavaScriptException e = new JavaScriptException(jso); assertSame(jso, catchNative(wrapWithFinally(createThrowRunnable(e)))); assertTrue(keepFinallyAlive); @@ -178,7 +188,7 @@ public void testJavaNativeJavaSandwichCatch() { } private Throwable javaNativeJavaSandwich(RuntimeException e) { - return catchJava(createThrowNativeRunnable(catchJava(createThrowRunnable(e)))); + return catchJava(createThrowNativeRunnable(catchNative(createThrowRunnable(e)))); } public void testCatchThrowNative() { @@ -224,16 +234,6 @@ public void testNativeJavaNativeSandwichCatch() { e = makeJavaObject(); assertSame(e, nativeJavaNativeSandwich(e)); - - e = new RuntimeException(); - assertSame(e, nativeJavaNativeSandwich(e)); - - e = new JavaScriptException("exception message"); // Thrown is not set - assertSame(e, nativeJavaNativeSandwich(e)); - - JavaScriptObject jso = makeJSO(); - e = new JavaScriptException(jso); - assertSame(jso, nativeJavaNativeSandwich(e)); } private Object nativeJavaNativeSandwich(Object e) { diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceCreatorCollectorTest.java b/user/test/com/google/gwt/core/client/impl/StackTraceCreatorCollectorTest.java index ce64fda7442..81a72d9e254 100644 --- a/user/test/com/google/gwt/core/client/impl/StackTraceCreatorCollectorTest.java +++ b/user/test/com/google/gwt/core/client/impl/StackTraceCreatorCollectorTest.java @@ -206,11 +206,12 @@ private static StackTraceElement createSTE(String methodName, String fileName, i } private static void assertStackTrace(JavaScriptObject jsError, StackTraceElement[] expected) { - assertEquals(expected, new CollectorModern().getStackTrace(toException(jsError))); + assertEquals(expected, new CollectorModern().getStackTrace(link(new Throwable(), jsError))); } - private static native Object toException(JavaScriptObject jsError) /*-{ - return { __gwt$backingJsError: jsError }; + private static native Object link(Throwable t, JavaScriptObject jsError) /*-{ + t.@Throwable::backingJsObject = jsError; + return t; }-*/; private static void assertEquals(StackTraceElement[] expecteds, StackTraceElement[] actuals) { diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceEmulTest.java b/user/test/com/google/gwt/core/client/impl/StackTraceEmulTest.java index ec7a538bfdf..b15ce7563b8 100644 --- a/user/test/com/google/gwt/core/client/impl/StackTraceEmulTest.java +++ b/user/test/com/google/gwt/core/client/impl/StackTraceEmulTest.java @@ -48,11 +48,11 @@ public void testJseLineNumbers() { String[] methodNames = getTraceJse(TYPE_ERROR); StackTraceElement[] expectedTrace = new StackTraceElement[] { - createSTE(methodNames[0], "StackTraceExamples.java", 80), - createSTE(methodNames[1], "StackTraceExamples.java", 76), - createSTE(methodNames[2], "StackTraceExamples.java", 92), - createSTE(methodNames[3], "StackTraceExamples.java", 58), - createSTE(methodNames[4], "StackTraceExamples.java", 49), + createSTE(methodNames[0], "StackTraceExamples.java", 83), + createSTE(methodNames[1], "StackTraceExamples.java", 79), + createSTE(methodNames[2], "StackTraceExamples.java", 95), + createSTE(methodNames[3], "StackTraceExamples.java", 61), + createSTE(methodNames[4], "StackTraceExamples.java", 52), createSTE(methodNames[5], "StackTraceExamples.java", 40) }; @@ -67,12 +67,11 @@ public void testJavaLineNumbers() { String[] methodNames = getTraceJava(); StackTraceElement[] expectedTrace = new StackTraceElement[] { - createSTE(methodNames[0], "Throwable.java", 114), - createSTE(methodNames[1], "Throwable.java", 58), - createSTE(methodNames[2], "Exception.java", 29), - createSTE(methodNames[3], "StackTraceExamples.java", 54), - createSTE(methodNames[4], "StackTraceExamples.java", 49), - createSTE(methodNames[5], "StackTraceExamples.java", 40) + createSTE(methodNames[0], "Throwable.java", 68), + createSTE(methodNames[1], "Exception.java", 29), + createSTE(methodNames[2], "StackTraceExamples.java", 57), + createSTE(methodNames[3], "StackTraceExamples.java", 52), + createSTE(methodNames[4], "StackTraceExamples.java", 40) }; assertTrace(expectedTrace, exception); @@ -130,6 +129,7 @@ private static StackTraceElement[] sample() { } private static StackTraceElement createSTE(String methodName, String fileName, int lineNumber) { + methodName = methodName.startsWith("?") ? methodName.substring(1) : methodName; return new StackTraceElement("Unknown", methodName, fileName, lineNumber); } } diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceExamples.java b/user/test/com/google/gwt/core/client/impl/StackTraceExamples.java index 7d6750f350b..4d86bfc4379 100644 --- a/user/test/com/google/gwt/core/client/impl/StackTraceExamples.java +++ b/user/test/com/google/gwt/core/client/impl/StackTraceExamples.java @@ -41,6 +41,9 @@ public static Exception getLiveException(Object whatToThrow) { fail("No exception thrown"); return null; // shouldn't happen } catch (Exception e) { + if (e.getStackTrace().length == 0) { + e.fillInStackTrace(); + } return e; } } diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceNativeTest.java b/user/test/com/google/gwt/core/client/impl/StackTraceNativeTest.java index ba083c22efc..d800a6c7a89 100644 --- a/user/test/com/google/gwt/core/client/impl/StackTraceNativeTest.java +++ b/user/test/com/google/gwt/core/client/impl/StackTraceNativeTest.java @@ -35,10 +35,10 @@ public String getModuleName() { @Override protected String[] getTraceJava() { + // First two frames are optional as they are automatically stripped by EDGE. return new String[] { - Impl.getNameOf("@java.lang.Throwable::fillInStackTrace()"), - Impl.getNameOf("@java.lang.Throwable::new(Ljava/lang/String;)"), - Impl.getNameOf("@java.lang.Exception::new(Ljava/lang/String;)"), + "?" + Impl.getNameOf("@java.lang.Throwable::new(Ljava/lang/String;)"), + "?" + Impl.getNameOf("@java.lang.Exception::new(Ljava/lang/String;)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::throwException2(*)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::throwException1(*)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::getLiveException(*)"), @@ -48,10 +48,10 @@ protected String[] getTraceJava() { @Override protected String[] getTraceRecursion() { + // First two frames are optional as they are automatically stripped by EDGE. final String[] expectedModern = { - Impl.getNameOf("@java.lang.Throwable::fillInStackTrace()"), - Impl.getNameOf("@java.lang.Throwable::new(Ljava/lang/String;)"), - Impl.getNameOf("@java.lang.Exception::new(Ljava/lang/String;)"), + "?" + Impl.getNameOf("@java.lang.Throwable::new(Ljava/lang/String;)"), + "?" + Impl.getNameOf("@java.lang.Exception::new(Ljava/lang/String;)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::throwException2(*)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::throwException1(*)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::throwRecursive(*)"), @@ -64,7 +64,6 @@ protected String[] getTraceRecursion() { }; final String[] expectedLegacy = { - Impl.getNameOf("@java.lang.Throwable::fillInStackTrace()"), Impl.getNameOf("@java.lang.Throwable::new(Ljava/lang/String;)"), Impl.getNameOf("@java.lang.Exception::new(Ljava/lang/String;)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::throwException2(*)"), @@ -88,16 +87,22 @@ protected String[] getTraceJse(Object thrown) { Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceTestBase::assertJse(*)"), }; - final String[] limited = { + final String[] limited_wrap = { Impl.getNameOf("@com.google.gwt.lang.Exceptions::wrap(*)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::getLiveException(*)"), Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceTestBase::assertJse(*)"), }; + final String[] limited_fillInStackTrace = { + Impl.getNameOf("@java.lang.Throwable::fillInStackTrace()"), + Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceExamples::getLiveException(*)"), + Impl.getNameOf("@com.google.gwt.core.client.impl.StackTraceTestBase::assertJse(*)"), + }; + // For legacy browsers and non-error javascript exceptions (e.g. throw "string"), we can only // construct stack trace from the catch block and below. - - return (isLegacyCollector() || thrown != TYPE_ERROR) ? limited : full; + return isLegacyCollector() + ? limited_wrap : (thrown != TYPE_ERROR ? limited_fillInStackTrace : full); } // TODO(goktug): new Error().stack is broken for htmlunit: diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceTestBase.java b/user/test/com/google/gwt/core/client/impl/StackTraceTestBase.java index 860a92b292c..c33d57a285d 100644 --- a/user/test/com/google/gwt/core/client/impl/StackTraceTestBase.java +++ b/user/test/com/google/gwt/core/client/impl/StackTraceTestBase.java @@ -68,15 +68,24 @@ private void assertJse(Object whatToThrow) { protected abstract String[] getTraceJse(Object whatToThrow); private void assertTrace(String[] expected, Exception t) { + int i = 0; StackTraceElement[] trace = t.getStackTrace(); - for (int i = 0; i < expected.length; i++) { + for (String expectedMethodName : expected) { + boolean optionalFrame = expectedMethodName.startsWith("?"); + if (optionalFrame) { + expectedMethodName = expectedMethodName.substring(1); + } StackTraceElement actualElement = trace[i]; String methodName = actualElement == null ? "!MISSING!" : actualElement.getMethodName(); - if (expected[i].equals(methodName)) { + if (expectedMethodName.equals(methodName)) { + i++; + continue; + } + if (optionalFrame) { continue; } AssertionFailedError e = new AssertionFailedError("Incorrect frame at " + i + " - " - + " Expected: " + expected[i] + " Actual: " + methodName); + + " Expected: " + expectedMethodName + " Actual: " + methodName); e.initCause(t); throw e; }