1
votes

I rebase a class with the following code:

DynamicType.Unloaded unloaded = new ByteBuddy().with(TypeValidation.DISABLED).rebase(typeDescription,
                ClassFileLocator.Simple.of(className, classBytes,
                        ClassFileLocator.ForClassLoader.of((ClassLoader) classLoader))).method(
                ElementMatchers.isPackagePrivate().and(ElementMatchers.not(ElementMatchers.isAbstract()))).intercept(
                MethodDelegation.to(PackagePrivateInterceptor.class)).transform(
                MethodTransformer.Simple.withModifiers(Visibility.PUBLIC)).make();
        return new ClassPair(unloaded.load((ClassLoader) classLoader,
                ClassLoadingStrategy.Default.INJECTION.withProtectionDomain(
                        classLoader.getClass().getProtectionDomain())).getLoaded(), unloaded.getBytes());

When loading the class class com.google.gson.internal.ConstructorConstructor

it goes through the constructor until finally it gets to the class initializer of com.google.gson.internal.LinkedTreeMap.

During that initialization, I get a VerifyError:

1:24:20.227 [main] ERROR   [io.hakansson.dynamicjar.core.main.Bootstrap] - java.lang.RuntimeException: java.lang.VerifyError: Illegal type at constant pool entry 195 in class com.google.gson.internal.LinkedTreeMap$1
Exception Details:
  Location:
    com/google/gson/internal/LinkedTreeMap$1.thenComparing$accessor$vT023QbO(Ljava/util/function/Function;)Ljava/util/Comparator; @2: invokespecial
  Reason:
    Constant pool index 195 is invalid
  Bytecode:
    0x0000000: 2a2b b700 c3b0                         

    at io.hakansson.dynamicjar.core.api.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:52)
    at io.hakansson.dynamicjar.core.main.Bootstrap.main(Bootstrap.java:42)
Caused by: java.lang.VerifyError: Illegal type at constant pool entry 195 in class com.google.gson.internal.LinkedTreeMap$1
Exception Details:
  Location:
    com/google/gson/internal/LinkedTreeMap$1.thenComparing$accessor$vT023QbO(Ljava/util/function/Function;)Ljava/util/Comparator; @2: invokespecial
  Reason:
    Constant pool index 195 is invalid
  Bytecode:
    0x0000000: 2a2b b700 c3b0                         

    at com.google.gson.internal.LinkedTreeMap.classInitializer$oNOjADym(LinkedTreeMap.java:40)
    at com.google.gson.internal.LinkedTreeMap.(LinkedTreeMap.java)
    at com.google.gson.internal.ConstructorConstructor$13.construct$original$gt92dwVY(ConstructorConstructor.java:207)
    at com.google.gson.internal.ConstructorConstructor$13.construct$original$gt92dwVY$accessor$lof1omy8(ConstructorConstructor.java)
    at com.google.gson.internal.ConstructorConstructor$13$auxiliary$oB71rVyd.call(Unknown Source)
    at io.hakansson.dynamicjar.nestedjarclassloader.PackagePrivateInterceptor.intercept(PackagePrivateInterceptor.java:29)
    at com.google.gson.internal.ConstructorConstructor$13.construct(ConstructorConstructor.java)
    at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:167)
    at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read$original$ZTjOtCtb(ReflectiveTypeAdapterFactory.java:116)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read$original$ZTjOtCtb$accessor$WgRdwpwl(ReflectiveTypeAdapterFactory.java)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1$auxiliary$41HBhnNS.call(Unknown Source)
    at io.hakansson.dynamicjar.nestedjarclassloader.PackagePrivateInterceptor.intercept(PackagePrivateInterceptor.java:29)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:216)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read$original$bOjIYDn5(TypeAdapterRuntimeTypeWrapper.java:40)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read$original$bOjIYDn5$accessor$hMWEZRZS(TypeAdapterRuntimeTypeWrapper.java)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper$auxiliary$U1tyihKy.call(Unknown Source)
    at io.hakansson.dynamicjar.nestedjarclassloader.PackagePrivateInterceptor.intercept(PackagePrivateInterceptor.java:29)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read$original$ZTjOtCtb(ReflectiveTypeAdapterFactory.java:116)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read$original$ZTjOtCtb$accessor$WgRdwpwl(ReflectiveTypeAdapterFactory.java)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1$auxiliary$41HBhnNS.call(Unknown Source)
    at io.hakansson.dynamicjar.nestedjarclassloader.PackagePrivateInterceptor.intercept(PackagePrivateInterceptor.java:29)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:216)
    at com.google.gson.Gson.fromJson(Gson.java:879)
    at com.google.gson.Gson.fromJson(Gson.java:817)

For reference, the interceptor basically just manually checks for package-private access (since package private methods were made public) and then just calls superCall.call(). I don't believe the issue lies there.

Any ideas?

EDIT: Here's the PackagePrivateInterceptor: public class PackagePrivateInterceptor {

@RuntimeType
@BindingPriority(1)
public static Object intercept(@SuperCall Callable<?> superCall, @Origin Class targetClass, @Origin String method) throws
        Exception
{

    Class callingClass = new InternalSecurityManager().getCallingClass();
    String targetPackage = targetClass.getPackage().getName();
    if (!callingClass.getPackage().getName().equals(targetPackage)) {
        throw new IllegalAccessError(callingClass + " cannot access method " + method + " of Class " + targetClass);
    }

    //Default:
    return superCall.call();
}

private static class InternalSecurityManager extends SecurityManager {
    Class getCallingClass() {
        Class[] classContext = getClassContext();
        for (Class current : classContext) {
            if (current.getName().startsWith("java.") ||
                    current.getName().equals(PackagePrivateInterceptor.class.getName()) ||
                    current.getName().equals(InternalSecurityManager.class.getName()))
            {
                continue;
            }
            return current;
        }
        throw new IllegalStateException("Failed to find calling Class");
    }
}

EDIT2: The following code does not trigger the issue:

//Only package-private methods should be proxied.
            DynamicType.Unloaded unloaded = new ByteBuddy().with(TypeValidation.DISABLED).rebase(typeDescription,
                    ClassFileLocator.Simple.of(className, classBytes,
                            ClassFileLocator.ForClassLoader.of((ClassLoader) classLoader))).method(
                    ElementMatchers.isPackagePrivate().and(ElementMatchers.not(ElementMatchers.isAbstract()))).intercept(
                    MethodDelegation.to(PackagePrivateInterceptor.class)).transform(
                    MethodTransformer.Simple.withModifiers(Visibility.PUBLIC)).make();
            DynamicType.Loaded loaded = unloaded.load((ClassLoader) classLoader,
                    ClassLoadingStrategy.Default.INJECTION.withProtectionDomain(
                            classLoader.getClass().getProtectionDomain()));
            if (className.equals("com.google.gson.internal.LinkedTreeMap"))
                System.out.println(DatatypeConverter.printHexBinary(loaded.getBytes()));
            return new ClassPair(loaded.getLoaded(), unloaded.getBytes());

But the following does:

//All non-private methods should be proxied
            //TODO: Actually, the class itself should be made visible and still only package-private methods should be proxied.
            DynamicType.Unloaded unloaded = new ByteBuddy().with(TypeValidation.DISABLED).rebase(typeDescription,
                    ClassFileLocator.Simple.of(className, classBytes,
                            ClassFileLocator.ForClassLoader.of((ClassLoader) classLoader))).method(
                    ElementMatchers.not(ElementMatchers.isPrivate()).and(
                            ElementMatchers.not(ElementMatchers.isAbstract()))).intercept(
                    MethodDelegation.to(PackagePrivateInterceptor.class)).transform(
                    MethodTransformer.Simple.withModifiers(Visibility.PUBLIC)).make();
            return new ClassPair(unloaded.load((ClassLoader) classLoader,
                    ClassLoadingStrategy.Default.INJECTION.withProtectionDomain(
                            classLoader.getClass().getProtectionDomain())).getLoaded(), unloaded.getBytes());
1
Could you produce a recreation of this error? Normally, this should not happen and I am not capable of creating this error. Byte Buddy uses ASM under the covers which takes care of the entire constant pool so I assume that there is a problem in the library. - Rafael Winterhalter
The method code is aload_0 aload_1 invokespecial [195] areturn which looks reasonable, i.e. not trashed. So the question is, what happened to the constant pool which ought to contain a method descriptor at index 195. Maybe posting the result of unloaded.getBytes() (as hex dump) could help… - Holger
@RafaelWinterHalter, I've tried to make a clean recreation but the problem does not arise. Obviously it's something that I do, but the thing is I don't do any manipulation of the bytes or anything. I'll investigate and try to see what exactly it is I do that triggers this error. - erikh
@Holger, Here is the hexdump of com/google/gson/internal/LinkedTreeMap: pastebin.com/76wg3SwK I have no knowledge of bytecode what so ever, so any help would be appreciated - erikh
Did you write any custom components? It looks to me like you register some form of custom transformer or Java agent that trashes your class file. - Rafael Winterhalter

1 Answers

2
votes

This is indeed a bug in Byte Buddy. The problem is that you are rebasing a Java 6 class on a Java 8 VM where the Comparable interface is implementing several default methods. You instruct Byte Buddy to override these methods and you also instruct the library to invoke these default methods from an overridden implementation. This is not legal for Java 6 class files even if the VM is running Java 8.

If I cange your matcher to:

not(isPrivate().or(isAbstract()).or(isDefaultMethod()))

the error goes away. I will fix this in a future version of Byte Buddy.

This error is currently not catched by either Byte Buddy or ASM which yields the strange error message.

Update: The error is now resolved in version 1.4.13.