diff --git a/core/pom.xml b/core/pom.xml index 72d6ee02b8..a8bb073070 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -107,7 +107,8 @@ test test - -Dguice_custom_class_loading=ANONYMOUS + + -Dguice_custom_class_loading=ANONYMOUS -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames diff --git a/core/src/com/google/inject/internal/InternalFlags.java b/core/src/com/google/inject/internal/InternalFlags.java index 97d8d3e337..26a1e0e27b 100644 --- a/core/src/com/google/inject/internal/InternalFlags.java +++ b/core/src/com/google/inject/internal/InternalFlags.java @@ -58,13 +58,13 @@ public enum IncludeStackTraceOption { public enum CustomClassLoadingOption { /** * Define fast/enhanced types in the same class loader as their original type, never creates - * class loaders. Uses Unsafe.defineAnonymousClass to gain access to existing class loaders. + * class loaders. Uses {@link sun.misc.Unsafe} to gain access to existing class loaders. */ OFF, /** - * Define fast/enhanced types with Unsafe.defineAnonymousClass, never creates class loaders. - * This is faster than regular class loading and anonymous classes are easier to unload. + * Define fast/enhanced types anonymously as hidden nest-mates, never creates class loaders. + * This is faster than regular class loading and the resulting classes are easier to unload. * *

Note: with this option you cannot look up fast/enhanced types by name or mock/spy them. */ diff --git a/core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java b/core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java new file mode 100644 index 0000000000..87c47a6ba6 --- /dev/null +++ b/core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 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.inject.internal.aop; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * {@link ClassDefiner} that defines classes using {@code sun.misc.Unsafe#defineAnonymousClass}. + * + * @author mcculls@gmail.com (Stuart McCulloch) + */ +final class AnonymousClassDefiner implements ClassDefiner { + + private static final Object THE_UNSAFE; + private static final Method ANONYMOUS_DEFINE_METHOD; + + static { + try { + Class unsafeType = Class.forName("sun.misc.Unsafe"); + Field theUnsafeField = unsafeType.getDeclaredField("theUnsafe"); + theUnsafeField.setAccessible(true); + THE_UNSAFE = theUnsafeField.get(null); + ANONYMOUS_DEFINE_METHOD = + unsafeType.getMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public Class define(Class hostClass, byte[] bytecode) throws Exception { + return (Class) ANONYMOUS_DEFINE_METHOD.invoke(THE_UNSAFE, hostClass, bytecode, null); + } +} diff --git a/core/src/com/google/inject/internal/aop/ClassDefining.java b/core/src/com/google/inject/internal/aop/ClassDefining.java index 7b8958a1b4..9ac662c29a 100644 --- a/core/src/com/google/inject/internal/aop/ClassDefining.java +++ b/core/src/com/google/inject/internal/aop/ClassDefining.java @@ -44,14 +44,19 @@ public static Class define(Class hostClass, byte[] bytecode) throws Except return ClassDefinerHolder.INSTANCE.define(hostClass, bytecode); } - /** Returns true if the ClassDefiner has access to package-private members. */ + /** Returns true if the current class definer allows access to package-private members. */ public static boolean hasPackageAccess() { return ClassDefinerHolder.IS_UNSAFE; } - /** Does the given class host new types anonymously, meaning they are not visible by name? */ - public static boolean isAnonymousHost(Class hostClass) { - return ClassDefinerHolder.IS_UNSAFE && UnsafeClassDefiner.isAnonymousHost(hostClass); + /** Returns true if it's possible to load by name proxies defined from the given host. */ + public static boolean canLoadProxyByName(Class hostClass) { + return !ClassDefinerHolder.IS_UNSAFE || UnsafeClassDefiner.canLoadProxyByName(hostClass); + } + + /** Returns true if it's possible to downcast to proxies defined from the given host. */ + public static boolean canDowncastToProxy(Class hostClass) { + return !ClassDefinerHolder.IS_UNSAFE || UnsafeClassDefiner.canDowncastToProxy(hostClass); } /** Binds the preferred {@link ClassDefiner} instance. */ diff --git a/core/src/com/google/inject/internal/aop/Enhancer.java b/core/src/com/google/inject/internal/aop/Enhancer.java index 3c172287e1..c3bc63c26d 100644 --- a/core/src/com/google/inject/internal/aop/Enhancer.java +++ b/core/src/com/google/inject/internal/aop/Enhancer.java @@ -167,8 +167,8 @@ final class Enhancer extends AbstractGlueGenerator { super(hostClass, ENHANCER_BY_GUICE_MARKER); this.bridgeDelegates = bridgeDelegates; - // CHECKCAST(proxyName) fails when hosted anonymously; hostName works in that scenario - this.checkcastToProxy = ClassDefining.isAnonymousHost(hostClass) ? hostName : proxyName; + // with defineAnonymousClass we can't downcast to the proxy and must use host instead + this.checkcastToProxy = ClassDefining.canDowncastToProxy(hostClass) ? proxyName : hostName; } @Override @@ -222,11 +222,8 @@ private void setupInvokerTable(ClassWriter cw) { Handle trampolineHandle = new Handle(H_INVOKESTATIC, proxyName, TRAMPOLINE_NAME, TRAMPOLINE_DESCRIPTOR, false); - if (ClassDefining.isAnonymousHost(hostClass)) { - // proxy class is anonymous we can't create our lambda glue, store raw trampoline instead - mv.visitLdcInsn(trampolineHandle); - } else { - // otherwise generate lambda glue to make the raw trampoline look like an invoker table + if (ClassDefining.canLoadProxyByName(hostClass)) { + // generate lambda glue to make the raw trampoline look like an invoker table mv.visitMethodInsn( INVOKESTATIC, @@ -254,6 +251,10 @@ private void setupInvokerTable(ClassWriter cw) { "getTarget", "()Ljava/lang/invoke/MethodHandle;", false); + + } else { + // proxy class is hidden so we can't create our lambda glue, store raw trampoline instead + mv.visitLdcInsn(trampolineHandle); } mv.visitFieldInsn(PUTSTATIC, proxyName, INVOKERS_NAME, INVOKERS_DESCRIPTOR); diff --git a/core/src/com/google/inject/internal/aop/GeneratedClassDefiner.java b/core/src/com/google/inject/internal/aop/GeneratedClassDefiner.java new file mode 100644 index 0000000000..65465c871a --- /dev/null +++ b/core/src/com/google/inject/internal/aop/GeneratedClassDefiner.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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.inject.internal.aop; + +import java.util.function.BiFunction; + +/** + * {@link ClassDefiner} that defines classes using a generated access function. + * + * @author mcculls@gmail.com (Stuart McCulloch) + */ +final class GeneratedClassDefiner implements ClassDefiner { + + private final BiFunction> defineAccess; + + GeneratedClassDefiner(BiFunction> defineAccess) { + this.defineAccess = defineAccess; + } + + @Override + public Class define(Class hostClass, byte[] bytecode) throws Exception { + return defineAccess.apply(hostClass.getClassLoader(), bytecode); + } +} diff --git a/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java b/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java new file mode 100644 index 0000000000..8e95263752 --- /dev/null +++ b/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 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.inject.internal.aop; + +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * {@link ClassDefiner} that defines classes using {@code MethodHandles.Lookup#defineHiddenClass}. + * + * @author mcculls@gmail.com (Stuart McCulloch) + */ +final class HiddenClassDefiner implements ClassDefiner { + + private static final Object THE_UNSAFE; + private static final Object TRUSTED_LOOKUP_BASE; + private static final Object TRUSTED_LOOKUP_OFFSET; + private static final Method GET_OBJECT_METHOD; + private static final Object HIDDEN_CLASS_OPTIONS; + private static final Method HIDDEN_DEFINE_METHOD; + + static { + try { + Class unsafeType = Class.forName("sun.misc.Unsafe"); + Field theUnsafeField = unsafeType.getDeclaredField("theUnsafe"); + theUnsafeField.setAccessible(true); + THE_UNSAFE = theUnsafeField.get(null); + Field trustedLookupField = Lookup.class.getDeclaredField("IMPL_LOOKUP"); + Method baseMethod = unsafeType.getMethod("staticFieldBase", Field.class); + TRUSTED_LOOKUP_BASE = baseMethod.invoke(THE_UNSAFE, trustedLookupField); + Method offsetMethod = unsafeType.getMethod("staticFieldOffset", Field.class); + TRUSTED_LOOKUP_OFFSET = offsetMethod.invoke(THE_UNSAFE, trustedLookupField); + GET_OBJECT_METHOD = unsafeType.getMethod("getObject", Object.class, long.class); + HIDDEN_CLASS_OPTIONS = classOptions("NESTMATE"); + HIDDEN_DEFINE_METHOD = + Lookup.class.getMethod( + "defineHiddenClass", byte[].class, boolean.class, HIDDEN_CLASS_OPTIONS.getClass()); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public Class define(Class hostClass, byte[] bytecode) throws Exception { + Lookup trustedLookup = + (Lookup) GET_OBJECT_METHOD.invoke(THE_UNSAFE, TRUSTED_LOOKUP_BASE, TRUSTED_LOOKUP_OFFSET); + Lookup definedLookup = + (Lookup) + HIDDEN_DEFINE_METHOD.invoke( + trustedLookup.in(hostClass), bytecode, false, HIDDEN_CLASS_OPTIONS); + return definedLookup.lookupClass(); + } + + /** Creates {@link MethodHandles.Lookup.ClassOption} array with the named options. */ + @SuppressWarnings("unchecked") + private static Object classOptions(String... options) throws ClassNotFoundException { + @SuppressWarnings("rawtypes") // Unavoidable, only way to use Enum.valueOf + Class optionClass = Class.forName(Lookup.class.getName() + "$ClassOption"); + Object classOptions = Array.newInstance(optionClass, options.length); + for (int i = 0; i < options.length; i++) { + Array.set(classOptions, i, Enum.valueOf(optionClass, options[i])); + } + return classOptions; + } +} diff --git a/core/src/com/google/inject/internal/aop/UnsafeClassDefiner.java b/core/src/com/google/inject/internal/aop/UnsafeClassDefiner.java index 56146319d9..dbb40cb6fc 100644 --- a/core/src/com/google/inject/internal/aop/UnsafeClassDefiner.java +++ b/core/src/com/google/inject/internal/aop/UnsafeClassDefiner.java @@ -17,15 +17,17 @@ package com.google.inject.internal.aop; import static java.lang.reflect.Modifier.PUBLIC; -import static java.lang.reflect.Modifier.STATIC; import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; import static org.objectweb.asm.Opcodes.ACC_SUPER; import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ARRAYLENGTH; +import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.V1_8; import com.google.common.cache.CacheBuilder; @@ -33,14 +35,14 @@ import com.google.common.cache.LoadingCache; import com.google.inject.internal.InternalFlags; import com.google.inject.internal.InternalFlags.CustomClassLoadingOption; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedExceptionAction; +import java.util.function.BiFunction; import java.util.logging.Level; import java.util.logging.Logger; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; /** * {@link ClassDefiner} that defines classes using {@code sun.misc.Unsafe}. @@ -51,86 +53,78 @@ final class UnsafeClassDefiner implements ClassDefiner { private static final Logger logger = Logger.getLogger(UnsafeClassDefiner.class.getName()); - private static final Object THE_UNSAFE = - tryPrivileged(UnsafeClassDefiner::bindTheUnsafe, "Cannot bind the Unsafe instance"); + private static final ClassDefiner UNSAFE_DEFINER; - private static final Method ANONYMOUS_DEFINE_METHOD = - tryPrivileged( - UnsafeClassDefiner::bindAnonymousDefineMethod, "Cannot bind Unsafe.defineAnonymousClass"); + static { + ClassDefiner unsafeDefiner = + tryPrivileged(AnonymousClassDefiner::new, "Cannot bind Unsafe.defineAnonymousClass"); + if (unsafeDefiner == null) { + unsafeDefiner = + tryPrivileged( + HiddenClassDefiner::new, "Cannot bind MethodHandles.Lookup.defineHiddenClass"); + } + UNSAFE_DEFINER = unsafeDefiner; + } private static final boolean ALWAYS_DEFINE_ANONYMOUSLY = InternalFlags.getCustomClassLoadingOption() == CustomClassLoadingOption.ANONYMOUS; private static final String DEFINEACCESS_BY_GUICE_MARKER = "$$DefineAccessByGuice$$"; + private static final String[] DEFINEACCESS_API = {"java/util/function/BiFunction"}; + + private static final String CLASS_LOADER_TYPE = Type.getInternalName(ClassLoader.class); + + private static final String BYTE_ARRAY_TYPE = Type.getInternalName(byte[].class); + // initialization-on-demand... - private static class ClassLoaderDefineMethodHolder { - static final Method CLASS_LOADER_DEFINE_METHOD = + private static class ClassLoaderDefineClassHolder { + static final ClassDefiner CLASS_LOADER_DEFINE_CLASS = tryPrivileged( - () -> accessDefineMethod(ClassLoader.class), "Cannot access ClassLoader.defineClass"); + () -> accessDefineClass(ClassLoader.class), "Cannot access ClassLoader.defineClass"); } // initialization-on-demand... - private static class DefineMethodCacheHolder { - static final LoadingCache, Method> DEFINE_METHOD_CACHE = + private static class DefineClassCacheHolder { + static final LoadingCache, ClassDefiner> DEFINE_CLASS_CACHE = CacheBuilder.newBuilder() .weakKeys() - .weakValues() - .build(CacheLoader.from(UnsafeClassDefiner::tryAccessDefineMethod)); + .build(CacheLoader.from(UnsafeClassDefiner::tryAccessDefineClass)); } /** Do we have access to {@code sun.misc.Unsafe}? */ public static boolean isAccessible() { - return ANONYMOUS_DEFINE_METHOD != null; + return UNSAFE_DEFINER != null; + } + + /** Returns true if it's possible to load by name proxies defined from the given host. */ + public static boolean canLoadProxyByName(Class hostClass) { + return findClassDefiner(hostClass.getClassLoader()) != UNSAFE_DEFINER; } - /** Does the given class host new types anonymously, ie. by using defineAnonymousClass? */ - @SuppressWarnings("ReferenceEquality") // intentional - public static boolean isAnonymousHost(Class hostClass) { - return findDefineMethod(hostClass.getClassLoader()) == ANONYMOUS_DEFINE_METHOD; + /** Returns true if it's possible to downcast to proxies defined from the given host. */ + public static boolean canDowncastToProxy(Class hostClass) { + return !(findClassDefiner(hostClass.getClassLoader()) instanceof AnonymousClassDefiner); } - @SuppressWarnings("ReferenceEquality") // intentional @Override public Class define(Class hostClass, byte[] bytecode) throws Exception { - ClassLoader hostLoader = hostClass.getClassLoader(); - Method defineMethod = findDefineMethod(hostLoader); - if (defineMethod == ANONYMOUS_DEFINE_METHOD) { - return defineAnonymously(hostClass, bytecode); - } - return (Class) defineMethod.invoke(null, hostLoader, bytecode); + return findClassDefiner(hostClass.getClassLoader()).define(hostClass, bytecode); } - /** Finds the appropriate class defining method for the given class loader. */ - private static Method findDefineMethod(ClassLoader hostLoader) { + /** Finds the appropriate class definer for the given class loader. */ + private static ClassDefiner findClassDefiner(ClassLoader hostLoader) { if (hostLoader == null || ALWAYS_DEFINE_ANONYMOUSLY) { - return ANONYMOUS_DEFINE_METHOD; - } else if (ClassLoaderDefineMethodHolder.CLASS_LOADER_DEFINE_METHOD != null) { + return UNSAFE_DEFINER; + } else if (ClassLoaderDefineClassHolder.CLASS_LOADER_DEFINE_CLASS != null) { // we have access to the defineClass method of anything extending ClassLoader - return ClassLoaderDefineMethodHolder.CLASS_LOADER_DEFINE_METHOD; + return ClassLoaderDefineClassHolder.CLASS_LOADER_DEFINE_CLASS; } else { // can't access ClassLoader, try accessing the specific sub-class instead - return DefineMethodCacheHolder.DEFINE_METHOD_CACHE.getUnchecked(hostLoader.getClass()); + return DefineClassCacheHolder.DEFINE_CLASS_CACHE.getUnchecked(hostLoader.getClass()); } } - private static Class defineAnonymously(Class hostClass, byte[] bytecode) throws Exception { - return (Class) ANONYMOUS_DEFINE_METHOD.invoke(THE_UNSAFE, hostClass, bytecode, null); - } - - private static Object bindTheUnsafe() throws Exception { - Class unsafeType = Class.forName("sun.misc.Unsafe"); - Field theUnsafeField = unsafeType.getDeclaredField("theUnsafe"); - theUnsafeField.setAccessible(true); - return theUnsafeField.get(null); - } - - private static Method bindAnonymousDefineMethod() throws Exception { - Class unsafeType = THE_UNSAFE.getClass(); - // defineAnonymousClass has all the functionality we need and is available in Java 7 onwards - return unsafeType.getMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class); - } - static T tryPrivileged(PrivilegedExceptionAction action, String errorMessage) { try { return AccessController.doPrivileged(action); @@ -140,22 +134,24 @@ static T tryPrivileged(PrivilegedExceptionAction action, String errorMess } } - static Method tryAccessDefineMethod(Class loaderClass) { + static ClassDefiner tryAccessDefineClass(Class loaderClass) { try { logger.log(Level.FINE, "Accessing defineClass method in %s", loaderClass); return AccessController.doPrivileged( - (PrivilegedExceptionAction) () -> accessDefineMethod(loaderClass)); + (PrivilegedExceptionAction) () -> accessDefineClass(loaderClass)); } catch (Throwable e) { logger.log(Level.FINE, "Cannot access defineClass method in " + loaderClass, e); - return ANONYMOUS_DEFINE_METHOD; + return UNSAFE_DEFINER; } } /** Generates helper in same package as the {@link ClassLoader} so it can access defineClass */ - static Method accessDefineMethod(Class loaderClass) throws Exception { + @SuppressWarnings("unchecked") + static ClassDefiner accessDefineClass(Class loaderClass) throws Exception { byte[] bytecode = buildDefineClassAccess(loaderClass); - Class accessClass = defineAnonymously(loaderClass, bytecode); - return accessClass.getMethod("defineClass", ClassLoader.class, byte[].class); + Class accessClass = UNSAFE_DEFINER.define(loaderClass, bytecode); + return new GeneratedClassDefiner( + (BiFunction>) accessClass.newInstance()); } /** {@link ClassLoader} helper that sits in the same package and passes on defineClass requests */ @@ -169,23 +165,36 @@ private static byte[] buildDefineClassAccess(Class loaderClass) { loaderClass.getName().replace('.', '/') + DEFINEACCESS_BY_GUICE_MARKER, null, "java/lang/Object", - null); + DEFINEACCESS_API); + + MethodVisitor mv; + + mv = cw.visitMethod(PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); - MethodVisitor mv = + mv = cw.visitMethod( - PUBLIC | STATIC, - "defineClass", - "(Ljava/lang/ClassLoader;[B)Ljava/lang/Class;", + PUBLIC, + "apply", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null, null); mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitInsn(ACONST_NULL); mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, CLASS_LOADER_TYPE); + mv.visitInsn(ACONST_NULL); + mv.visitVarInsn(ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, BYTE_ARRAY_TYPE); mv.visitInsn(ICONST_0); - mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, BYTE_ARRAY_TYPE); mv.visitInsn(ARRAYLENGTH); mv.visitMethodInsn( diff --git a/core/test/com/google/inject/ImplicitBindingTest.java b/core/test/com/google/inject/ImplicitBindingTest.java index 0b7e4262fa..8801b2133f 100644 --- a/core/test/com/google/inject/ImplicitBindingTest.java +++ b/core/test/com/google/inject/ImplicitBindingTest.java @@ -437,6 +437,8 @@ public void testImplicitJdkBindings() { // String has a public nullary constructor, so Guice will call it. assertEquals("", injector.getInstance(String.class)); // InetAddress has a package private constructor. We probably shouldn't be calling it :( - assertNotNull(injector.getInstance(java.net.InetAddress.class)); + if (Double.parseDouble(System.getProperty("java.specification.version")) < 17) { + assertNotNull(injector.getInstance(java.net.InetAddress.class)); + } } } diff --git a/core/test/com/google/inject/MethodInterceptionTest.java b/core/test/com/google/inject/MethodInterceptionTest.java index 9e4accd369..5f6db7e851 100644 --- a/core/test/com/google/inject/MethodInterceptionTest.java +++ b/core/test/com/google/inject/MethodInterceptionTest.java @@ -259,10 +259,14 @@ protected void configure() { // validate all causes. for (Throwable t = e; t != null; t = t.getCause()) { StackTraceElement[] stackTraceElement = t.getStackTrace(); - assertEquals("explode", stackTraceElement[0].getMethodName()); - assertEquals("invoke", stackTraceElement[1].getMethodName()); - assertEquals("invoke", stackTraceElement[2].getMethodName()); - assertEquals("testInterceptedMethodThrows", stackTraceElement[3].getMethodName()); + int frame = 0; + assertEquals("explode", stackTraceElement[frame++].getMethodName()); + while (stackTraceElement[frame].getClassName().startsWith("java.lang.invoke.LambdaForm")) { + frame++; // ignore lambda frames when running tests with ShowHiddenFrames + } + assertEquals("invoke", stackTraceElement[frame++].getMethodName()); + assertEquals("invoke", stackTraceElement[frame++].getMethodName()); + assertEquals("testInterceptedMethodThrows", stackTraceElement[frame++].getMethodName()); } } }