Start tracking suppressed errors in addition to the cause in underlying error object.
Change-Id: I82806154c1fceee612d06fbc94ccfe2a34268e74
Review-Link: https://gwt-review.googlesource.com/#/c/22020/
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 3ea313d..cbee36f 100644
--- a/user/super/com/google/gwt/emul/java/lang/Throwable.java
+++ b/user/super/com/google/gwt/emul/java/lang/Throwable.java
@@ -21,8 +21,8 @@
import java.io.PrintStream;
import java.io.Serializable;
+import java.util.Arrays;
-import javaemul.internal.JsUtils;
import javaemul.internal.annotations.DoNotInline;
import jsinterop.annotations.JsMethod;
@@ -141,23 +141,39 @@
private void setBackingJsObject(Object backingJsObject) {
this.backingJsObject = backingJsObject;
linkBack(backingJsObject);
- linkBackingCause();
}
- private void linkBack(Object error) {
- if (error != null) {
+ private native void linkBack(Object error) /*-{
+
+ if (error instanceof Object) {
try {
// This may throw exception in strict mode.
- ((HasJavaThrowable) error).setJavaThrowable(this);
- } catch (Throwable ignored) { }
- }
- }
+ error.__java$exception = this;
- private void linkBackingCause() {
- if (cause == null || !(backingJsObject instanceof NativeError)) {
- return;
+ if (navigator.userAgent.toLowerCase().indexOf("msie") != -1 && $doc.documentMode < 9) {
+ return;
+ }
+
+ var throwable = this;
+ Object.defineProperties(error, {
+ cause: {
+ get: function() {
+ var cause = throwable.@Throwable::getCause()();
+ return cause && cause.@Throwable::getBackingJsObject()();
+ }
+ },
+ suppressed: {
+ get: function() {
+ return throwable.@Throwable::getBackingSuppressed()();
+ }
+ }
+ });
+ } catch (ignored) {}
}
- JsUtils.setProperty(backingJsObject, "cause", cause.backingJsObject);
+ }-*/;
+
+ private Object[] getBackingSuppressed() {
+ return Arrays.stream(getSuppressed()).map(t -> t.backingJsObject).toArray();
}
/**
@@ -245,7 +261,6 @@
checkState(this.cause == null, "Can't overwrite cause");
checkCriticalArgument(cause != this, "Self-causation not permitted");
this.cause = cause;
- linkBackingCause();
return this;
}
@@ -323,9 +338,6 @@
@JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
private interface HasJavaThrowable {
@JsProperty(name = "__java$exception")
- void setJavaThrowable(Throwable t);
-
- @JsProperty(name = "__java$exception")
Throwable getJavaThrowable();
}
}
diff --git a/user/test/com/google/gwt/emultest/java/lang/JsExceptionTest.java b/user/test/com/google/gwt/emultest/java/lang/JsExceptionTest.java
index 0c2abbb..511520d 100644
--- a/user/test/com/google/gwt/emultest/java/lang/JsExceptionTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/JsExceptionTest.java
@@ -129,11 +129,25 @@
@JsType(isNative = true, namespace = "<window>")
private static class TypeError { }
- protected static void assertTypeError(RuntimeException e) {
+ private static void assertTypeError(RuntimeException e) {
assertTrue(getBackingJsObject(e) instanceof TypeError);
assertTrue(e.toString().contains("TypeError"));
}
+ public void testFrozenObject() {
+ try {
+ Object e = new TypeError();
+ freeze(e);
+ throwNative(e);
+ fail();
+ } catch (RuntimeException e) {
+ e = (RuntimeException) javaNativeJavaSandwich(e);
+ }
+ }
+
+ @JsMethod(name = "Object.freeze", namespace = JsPackage.GLOBAL)
+ private static native void freeze(Object o);
+
public void testSvgError() {
try {
throwSvgError();
diff --git a/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java b/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java
index ae01e46..3a6475b 100644
--- a/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java
@@ -16,6 +16,7 @@
package com.google.gwt.emultest.java.lang;
import com.google.gwt.testing.TestUtils;
+import java.io.IOException;
import jsinterop.annotations.JsType;
/** Unit tests for the GWT emulation of java.lang.Throwable class. */
@@ -111,7 +112,7 @@
}
public void testLinkedBackingObjects() {
- if (TestUtils.isJvm()) {
+ if (isJvmOrIe8()) {
return;
}
Throwable rootCause = new Throwable("Root cause");
@@ -121,7 +122,7 @@
}
public void testLinkedBackingObjects_initCause() {
- if (TestUtils.isJvm()) {
+ if (isJvmOrIe8()) {
return;
}
Throwable rootCause = new Throwable("Root cause");
@@ -132,7 +133,7 @@
}
public void testLinkedBackingObjects_noCause() {
- if (TestUtils.isJvm()) {
+ if (isJvmOrIe8()) {
return;
}
Throwable subError = new Throwable("Sub-error");
@@ -140,6 +141,45 @@
assertNull(getBackingJsObject(subError).getCause());
}
+ public void testLinkedSuppressedErrors_suppressedAddedViaInit() {
+ if (isJvmOrIe8()) {
+ return;
+ }
+ final Throwable suppressed = new Throwable();
+ Throwable e =
+ new Throwable() {
+ {
+ addSuppressed(suppressed);
+ }
+ };
+
+ assertEquals(getBackingJsObject(suppressed), getBackingJsObject(e).getSuppressed()[0]);
+ }
+
+ public void testLinkedSuppressedErrors_tryWithResources() {
+ if (isJvmOrIe8()) {
+ return;
+ }
+
+ class FailingResource implements AutoCloseable {
+ @Override
+ public void close() throws IOException {
+ throw new IOException("onClose");
+ }
+ }
+
+ RuntimeException e = new RuntimeException("try");
+ try (FailingResource r = new FailingResource()) {
+ throw e;
+ } catch (Exception expected) { }
+
+ assertEquals(
+ getBackingJsObject(e.getSuppressed()[0]), getBackingJsObject(e).getSuppressed()[0]);
+ }
+
+ private static boolean isJvmOrIe8() {
+ return TestUtils.isJvm() || System.getProperty("user.agent", "safari").equals("ie8");
+ }
@JsType(isNative = true, name = "Error", namespace = "<window>")
private static class JsError { }
diff --git a/user/test/com/google/gwt/emultest/java/lang/ThrowableTestBase.java b/user/test/com/google/gwt/emultest/java/lang/ThrowableTestBase.java
index 967eb5f..0397350 100644
--- a/user/test/com/google/gwt/emultest/java/lang/ThrowableTestBase.java
+++ b/user/test/com/google/gwt/emultest/java/lang/ThrowableTestBase.java
@@ -16,11 +16,10 @@
package com.google.gwt.emultest.java.lang;
import com.google.gwt.junit.client.GWTTestCase;
-import javaemul.internal.JsUtils;
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsMethod;
-import jsinterop.annotations.JsOverlay;
import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
@@ -90,14 +89,10 @@
/** A JavaScript object backing a Throwable. */
@JsType(isNative = true, name = "*", namespace = JsPackage.GLOBAL)
interface BackingJsObject {
- @JsOverlay
- default Object getCause() {
- return JsUtils.getProperty(this, "cause");
- }
+ @JsProperty
+ Object getCause();
- @JsOverlay
- default Object[] getSuppressed() {
- return JsUtils.getProperty(this, "suppressed");
- }
+ @JsProperty
+ Object[] getSuppressed();
}
}