Search code examples
javaoopreflectionjvm

Forcefully subclass a Java class with package-private constructor


I want to forcefully make a class in another library open to subclass. The class itself is public, but its only constructor is package-private. I have already explored generating a class with the bytecode to open the constructor and make the super() call. In JASM (super cool), the class looks like this:

public class com/internal/OpenChild
extends com/external/Parent
{
  public <init>()V {
    try {
      ldc com/external/Parent
      iconst 0
      anewarray java/lang/Class
      invokevirtual java/lang/Class.getDeclaredConstructor([java/lang/Class)java/lang/reflect/Constructor
      iconst 1
      invokevirtual java/lang/reflect/Constructor.setAccessible(Z)V
    } catch (java/lang/Throwable) {
      astore 0
      new java/lang/RuntimeException
      dup
      aload 0
      invokespecial java/lang/RuntimeException.<init>(java/lang/Throwable)V
      athrow
    }
    aload 0
    invokespecial com/external/Parent.<init>()V
    return
  }
}

This class, com.internal.OpenChild, extends com.external.Parent. Its public no-args constructor makes accessible the latter's package-private no-args constructor and follows with a super call. The equivalent Java may look something like this:

package com.internal;

import com.external.Parent;

public class OpenChild extends Parent {
  public OpenChild() {
    try {
      Parent.class.getDeclaredConstructor().setAccessible(true);
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
    super();
  }
}

While the reflection works fine, the super call fails with an IllegalAccessException. I believe I may have hope yet--after all, it's possible to use reflection to create an instance of com.external.Parent. What am I missing from my strategy? Is it not possible to invoke such black magic after all?


Solution

  • There seems to be a widespread misunderstanding about what the accessible property does.

    It does not alter the reflected member at all. It only enables access to it through the one instance on which setAccessible(true) has been called. This implies that other instances reflecting the same member (e.g. when other code sites query the class) still have their own accessible state, which is false by default, and classes trying to access the member via ordinary non-reflective access (i.e. bytecode instructions) are not affected.

    The only (official) way to actually alter classes is through Instrumentation

    Alternatively, you can inject a subclass into the same package which can access the package-private constructor. If your subclass needs to be in a different package, you can create an indirect subclass, i.e. inject a subclass into the package accessing the package-private constructor but having a public or protected constructor, accessible by the subclass of the subclass in the different package.