From e733216a84edad808354a8a2b478a1aaaba9d5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 6 Dec 2024 11:48:03 -0800 Subject: [PATCH] Rewrite and publish `Throwables.testLazyStackTraceFallback`. Previously it used a security manager to forbid access to the `sun.misc` APIs and thus force the use of the fallback logic. It also had some Google-internal logic that meant it couldn't be published. Now it uses a custom `ClassLoader` to achieve the same effect more portably. The code path tested here is mostly obsolete. It is really only relevant for JDK 8, so not on Android and not on any later JDK version. RELNOTES=n/a PiperOrigin-RevId: 703567939 --- .../google/common/base/ThrowablesTest.java | 19 ------ .../google/common/base/ThrowablesTest.java | 67 ++++++++++++++++--- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/android/guava-tests/test/com/google/common/base/ThrowablesTest.java b/android/guava-tests/test/com/google/common/base/ThrowablesTest.java index c8a6765a1077..6e00c08fb6a8 100644 --- a/android/guava-tests/test/com/google/common/base/ThrowablesTest.java +++ b/android/guava-tests/test/com/google/common/base/ThrowablesTest.java @@ -379,25 +379,6 @@ public void testLazyStackTrace() { assertThat(lazyStackTrace(e)).containsExactly((Object[]) originalStackTrace).inOrder(); } - @J2ktIncompatible - @GwtIncompatible // lazyStackTrace - private void doTestLazyStackTraceFallback() { - assertFalse(lazyStackTraceIsLazy()); - - Exception e = new Exception(); - - assertThat(lazyStackTrace(e)).containsExactly((Object[]) e.getStackTrace()).inOrder(); - - try { - lazyStackTrace(e).set(0, null); - fail(); - } catch (UnsupportedOperationException expected) { - } - - e.setStackTrace(new StackTraceElement[0]); - assertThat(lazyStackTrace(e)).isEmpty(); - } - @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { diff --git a/guava-tests/test/com/google/common/base/ThrowablesTest.java b/guava-tests/test/com/google/common/base/ThrowablesTest.java index c8a6765a1077..3482d30a63b2 100644 --- a/guava-tests/test/com/google/common/base/ThrowablesTest.java +++ b/guava-tests/test/com/google/common/base/ThrowablesTest.java @@ -29,6 +29,7 @@ import static com.google.common.base.Throwables.propagateIfPossible; import static com.google.common.base.Throwables.throwIfInstanceOf; import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static java.util.regex.Pattern.quote; @@ -41,9 +42,15 @@ import com.google.common.base.TestExceptions.SomeOtherCheckedException; import com.google.common.base.TestExceptions.SomeUncheckedException; import com.google.common.base.TestExceptions.YetAnotherCheckedException; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.primitives.Ints; import com.google.common.testing.NullPointerTester; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; import java.util.List; import junit.framework.TestCase; @@ -381,21 +388,61 @@ public void testLazyStackTrace() { @J2ktIncompatible @GwtIncompatible // lazyStackTrace - private void doTestLazyStackTraceFallback() { - assertFalse(lazyStackTraceIsLazy()); - - Exception e = new Exception(); - - assertThat(lazyStackTrace(e)).containsExactly((Object[]) e.getStackTrace()).inOrder(); - + public void testLazyStackTraceFallback() throws Exception { + // Make a parallel class loader that has the same classpath as the current class loader, but + // won't load classes from the sun.misc package. This test assumes that the Throwables class + // is coming from the classpath, which is a reasonable assumption for most test environments. + ImmutableList classPath = + Splitter.on(':') + .splitToStream(StandardSystemProperty.JAVA_CLASS_PATH.value()) + .map( + s -> { + try { + if (!s.startsWith("/")) { + s = StandardSystemProperty.USER_DIR.value() + "/" + s; + } + return new URL("file://" + s); + } catch (MalformedURLException e) { + throw new UncheckedIOException(e); + } + }) + .collect(toImmutableList()); + URL[] urls = classPath.toArray(new URL[0]); + ClassLoader parallelLoader = + new URLClassLoader(urls, getClass().getClassLoader().getParent()) { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (name.startsWith("sun.misc")) { + throw new ClassNotFoundException(name); + } + return super.loadClass(name); + } + }; + + // Now load a second version of the Throwables class from the parallel class loader and use + // reflection to call its methods. + Class parallelThrowables = parallelLoader.loadClass(Throwables.class.getCanonicalName()); + assertThat(parallelThrowables).isNotSameInstanceAs(Throwables.class); + Class.forName("sun.misc.Unsafe"); try { - lazyStackTrace(e).set(0, null); + parallelLoader.loadClass("sun.misc.Unsafe"); fail(); - } catch (UnsupportedOperationException expected) { + } catch (ClassNotFoundException expected) { + // Expected: the whole point is for the parallel loader not to load sun.misc classes. } + Method lazyStackTraceIsLazy = parallelThrowables.getMethod("lazyStackTraceIsLazy"); + assertThat((Boolean) lazyStackTraceIsLazy.invoke(null)).isFalse(); + + Method lazyStackTrace = parallelThrowables.getMethod("lazyStackTrace", Throwable.class); + Exception e = new Exception(); + List lazyStackTraceResult = (List) lazyStackTrace.invoke(null, e); + assertThat(lazyStackTraceResult).containsExactlyElementsIn(e.getStackTrace()).inOrder(); + + assertThrows(UnsupportedOperationException.class, () -> lazyStackTraceResult.set(0, null)); + e.setStackTrace(new StackTraceElement[0]); - assertThat(lazyStackTrace(e)).isEmpty(); + assertThat((List) lazyStackTrace.invoke(null, e)).isEmpty(); } @J2ktIncompatible