Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions core/src/com/google/inject/internal/InternalFlags.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
Expand Down
64 changes: 0 additions & 64 deletions core/src/com/google/inject/internal/aop/AnonymousClassDefiner.java

This file was deleted.

13 changes: 4 additions & 9 deletions core/src/com/google/inject/internal/aop/ClassDefining.java
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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. */
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions core/src/com/google/inject/internal/aop/Enhancer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 53 additions & 65 deletions core/src/com/google/inject/internal/aop/HiddenClassDefiner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
63 changes: 63 additions & 0 deletions core/src/com/google/inject/internal/aop/LookupClassDefiner.java
Original file line number Diff line number Diff line change
@@ -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 <T> T tryPrivileged(PrivilegedExceptionAction<T> action, String errorMessage) {
try {
return AccessController.doPrivileged(action);
} catch (Throwable e) {
logger.log(Level.FINE, errorMessage, e);
return null;
}
}
}
Loading