diff --git a/core/src/com/google/inject/internal/InternalFlags.java b/core/src/com/google/inject/internal/InternalFlags.java index ebaf26eeaa..e6034406f8 100644 --- a/core/src/com/google/inject/internal/InternalFlags.java +++ b/core/src/com/google/inject/internal/InternalFlags.java @@ -69,13 +69,6 @@ public enum CustomClassLoadingOption { */ OFF, - /** - * 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.
- */
- ANONYMOUS,
/**
* Attempt to define fast/enhanced types in the same class loader as their original type.
diff --git a/core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java b/core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java
deleted file mode 100644
index cc9fe49267..0000000000
--- a/core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.Method;
-
-/**
- * {@link ClassDefiner} that defines classes using {@code sun.misc.Unsafe#defineAnonymousClass}.
- *
- * @author mcculls@gmail.com (Stuart McCulloch)
- */
-@SuppressWarnings("SunApi")
-final class AnonymousClassDefiner implements ClassDefiner {
-
- private static final sun.misc.Unsafe THE_UNSAFE;
- private static final Method ANONYMOUS_DEFINE_METHOD;
-
- /** True if this class err'd during initialization and should not be used. */
- static final boolean HAS_ERROR;
-
- static {
- sun.misc.Unsafe theUnsafe;
- Method anonymousDefineMethod;
- try {
- theUnsafe = UnsafeGetter.getUnsafe();
- // defineAnonymousClass was removed in JDK17, so we must refer to it reflectively.
- anonymousDefineMethod =
- sun.misc.Unsafe.class.getMethod(
- "defineAnonymousClass", Class.class, byte[].class, Object[].class);
- } catch (ReflectiveOperationException e) {
- theUnsafe = null;
- anonymousDefineMethod = null;
- }
-
- THE_UNSAFE = theUnsafe;
- ANONYMOUS_DEFINE_METHOD = anonymousDefineMethod;
- HAS_ERROR = theUnsafe == null;
- }
-
- @Override
- public Class> define(Class> hostClass, byte[] bytecode) throws Exception {
- if (HAS_ERROR) {
- throw new IllegalStateException(
- "Should not be called. An earlier error occurred during AnonymousClassDefiner static"
- + " initialization.");
- }
-
- 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 b8f276fb9b..f5f5cc553a 100644
--- a/core/src/com/google/inject/internal/aop/ClassDefining.java
+++ b/core/src/com/google/inject/internal/aop/ClassDefining.java
@@ -36,7 +36,7 @@ private ClassDefining() {}
// initialization-on-demand...
private static class ClassDefinerHolder {
static final ClassDefiner INSTANCE = bindClassDefiner();
- static final boolean IS_UNSAFE = INSTANCE instanceof UnsafeClassDefiner;
+ static final boolean IS_UNSAFE = INSTANCE instanceof LookupClassDefiner;
}
/** Defines a new class relative to the host. */
@@ -51,12 +51,7 @@ public static boolean hasPackageAccess() {
/** 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);
+ return !ClassDefinerHolder.IS_UNSAFE || LookupClassDefiner.canLoadProxyByName(hostClass);
}
/** Binds the preferred {@link ClassDefiner} instance. */
@@ -65,8 +60,8 @@ static ClassDefiner bindClassDefiner() {
CustomClassLoadingOption loadingOption = InternalFlags.getCustomClassLoadingOption();
if (loadingOption == CustomClassLoadingOption.CHILD) {
return new ChildClassDefiner(); // override default choice
- } else if (UnsafeClassDefiner.isAccessible()) {
- return new UnsafeClassDefiner(); // default choice if available
+ } else if (LookupClassDefiner.isAccessible()) {
+ return new LookupClassDefiner(); // default choice if available
} else if (loadingOption != CustomClassLoadingOption.OFF) {
return new ChildClassDefiner(); // second choice unless forbidden
} else {
diff --git a/core/src/com/google/inject/internal/aop/Enhancer.java b/core/src/com/google/inject/internal/aop/Enhancer.java
index c3bc63c26d..6380da2a2c 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;
- // with defineAnonymousClass we can't downcast to the proxy and must use host instead
- this.checkcastToProxy = ClassDefining.canDowncastToProxy(hostClass) ? proxyName : hostName;
+ // Generated code can downcast to the generated proxy type.
+ this.checkcastToProxy = proxyName;
}
@Override
diff --git a/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java b/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java
index a63ec59533..3420691c44 100644
--- a/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java
+++ b/core/src/com/google/inject/internal/aop/HiddenClassDefiner.java
@@ -16,87 +16,75 @@
package com.google.inject.internal.aop;
+import java.lang.invoke.MethodHandles;
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)
*/
-@SuppressWarnings("SunApi")
final class HiddenClassDefiner implements ClassDefiner {
- private static final sun.misc.Unsafe THE_UNSAFE;
- private static final Object TRUSTED_LOOKUP_BASE;
- private static final long TRUSTED_LOOKUP_OFFSET;
- private static final Object HIDDEN_CLASS_OPTIONS;
- private static final Method HIDDEN_DEFINE_METHOD;
-
- /** True if this class err'd during initialization and should not be used. */
- static final boolean HAS_ERROR;
+ @Override
+ public Class> define(Class> hostClass, byte[] bytecode) throws Exception {
+ Module guiceModule = HiddenClassDefiner.class.getModule();
+ Module hostModule = hostClass.getModule();
+ if (guiceModule.isNamed() && hostModule.isNamed()) {
+ if (!guiceModule.canRead(hostModule)) {
+ guiceModule.addReads(hostModule);
+ }
+ if (!hostModule.isOpen(hostClass.getPackageName(), guiceModule)) {
+ hostModule.addOpens(hostClass.getPackageName(), guiceModule);
+ }
+ }
- static {
- sun.misc.Unsafe theUnsafe;
- Object trustedLookupBase;
- long trustedLookupOffset;
- Object hiddenClassOptions;
- Method hiddenDefineMethod;
+ Lookup initialLookup;
try {
- theUnsafe = UnsafeGetter.getUnsafe();
- Field trustedLookupField = Lookup.class.getDeclaredField("IMPL_LOOKUP");
- trustedLookupBase = theUnsafe.staticFieldBase(trustedLookupField);
- trustedLookupOffset = theUnsafe.staticFieldOffset(trustedLookupField);
- hiddenClassOptions = classOptions("NESTMATE");
- hiddenDefineMethod =
- Lookup.class.getMethod(
- "defineHiddenClass", byte[].class, boolean.class, hiddenClassOptions.getClass());
- } catch (Throwable e) {
- // Allow the static initialization to complete without
- // throwing an exception.
- theUnsafe = null;
- trustedLookupBase = null;
- trustedLookupOffset = 0;
- hiddenClassOptions = null;
- hiddenDefineMethod = null;
+ initialLookup = MethodHandles.privateLookupIn(hostClass, MethodHandles.lookup());
+ } catch (IllegalAccessException e) {
+ initialLookup = MethodHandles.lookup().in(hostClass);
}
-
- THE_UNSAFE = theUnsafe;
- TRUSTED_LOOKUP_BASE = trustedLookupBase;
- TRUSTED_LOOKUP_OFFSET = trustedLookupOffset;
- HIDDEN_CLASS_OPTIONS = hiddenClassOptions;
- HIDDEN_DEFINE_METHOD = hiddenDefineMethod;
- HAS_ERROR = theUnsafe == null;
+ return defineClass(initialLookup, bytecode, hostClass);
}
- @Override
- public Class> define(Class> hostClass, byte[] bytecode) throws Exception {
- if (HAS_ERROR) {
- throw new IllegalStateException(
- "Should not be called. An earlier error occurred during HiddenClassDefiner static"
- + " initialization.");
- }
+ private Class> defineClass(Lookup lookup, byte[] bytecode, Class> hostClass) throws Exception {
+ try {
+ return lookup.defineClass(bytecode);
+ } catch (IllegalAccessException e) {
+ // 1) Try hostClass.getModuleLookup() if the host exposes one.
+ try {
+ Method getModuleLookup = hostClass.getDeclaredMethod("getModuleLookup");
+ getModuleLookup.setAccessible(true);
+ Lookup nextLookup = (Lookup) getModuleLookup.invoke(null);
+ if (nextLookup != null && !nextLookup.equals(lookup)) {
+ return nextLookup.defineClass(bytecode);
+ }
+ } catch (Throwable ignored) {
+ // Ignore and continue with other lookup strategies.
+ }
- Lookup trustedLookup =
- (Lookup) THE_UNSAFE.getObject(TRUSTED_LOOKUP_BASE, TRUSTED_LOOKUP_OFFSET);
- Lookup definedLookup =
- (Lookup)
- HIDDEN_DEFINE_METHOD.invoke(
- trustedLookup.in(hostClass), bytecode, false, HIDDEN_CLASS_OPTIONS);
- return definedLookup.lookupClass();
- }
+ // 2) Retry with a private lookup.
+ try {
+ Lookup nextLookup = MethodHandles.privateLookupIn(hostClass, MethodHandles.lookup());
+ if (nextLookup != null && !nextLookup.equals(lookup)) {
+ return nextLookup.defineClass(bytecode);
+ }
+ } catch (IllegalAccessException ignored) {
+ // Ignore and continue with other lookup strategies.
+ }
+
+ // 3) Last attempt with lookup().in(hostClass).
+ Lookup fallbackLookup = MethodHandles.lookup().in(hostClass);
+ if (!fallbackLookup.equals(lookup)) {
+ try {
+ return fallbackLookup.defineClass(bytecode);
+ } catch (IllegalAccessException ignored) {
+ // Fall through to rethrow the original access error.
+ }
+ }
- /** 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]));
+ throw e;
}
- return classOptions;
}
}
diff --git a/core/src/com/google/inject/internal/aop/LookupClassDefiner.java b/core/src/com/google/inject/internal/aop/LookupClassDefiner.java
new file mode 100644
index 0000000000..505e37a1ac
--- /dev/null
+++ b/core/src/com/google/inject/internal/aop/LookupClassDefiner.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * {@link ClassDefiner} that defines classes using {@code java.lang.invoke.MethodHandles.Lookup}.
+ *
+ * @author mcculls@gmail.com (Stuart McCulloch)
+ */
+final class LookupClassDefiner implements ClassDefiner {
+
+ private static final Logger logger = Logger.getLogger(LookupClassDefiner.class.getName());
+
+ private static final ClassDefiner HIDDEN_DEFINER;
+
+ static {
+ HIDDEN_DEFINER = tryPrivileged(HiddenClassDefiner::new, "Cannot bind HiddenClassDefiner");
+ }
+
+ public static boolean isAccessible() {
+ return HIDDEN_DEFINER != null;
+ }
+
+ public static boolean canLoadProxyByName(Class> hostClass) {
+ return HIDDEN_DEFINER != null; // lookup-defined classes are normal named classes
+ }
+
+ @Override
+ public Class> define(Class> hostClass, byte[] bytecode) throws Exception {
+ if (HIDDEN_DEFINER == null) {
+ throw new IllegalStateException("No class definer available");
+ }
+ return HIDDEN_DEFINER.define(hostClass, bytecode);
+ }
+
+ static