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;
}