5

I was trying to change the value of a private static final field using reflection (yes, that's probably a very bad idea to begin with, I know). And, well, for the most part it works fine using following code:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class A {

    public static void main(String[] args) throws ReflectiveOperationException {
        System.out.println("Before :: " + B.get());
        Field field = B.class.getDeclaredField("arr");
        field.setAccessible(true);
        // System.out.println("Peek   :: " + ((String[]) field.get(null))[0]);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, new String[] { "Good bye, World!" });
        System.out.println("After  :: " + B.get());
    }
}

class B {
    private static final String[] arr = new String[] { "Hello, World!" };

    public static String get() {
        return arr[0];
    }
}

Which prints as expected:

Before :: Hello, World!
After  :: Good bye, World!

Problems arise when I try to get the field value via reflection prior to setting it. That is, if I uncomment the commented line in above sample, I get the following:

Before :: Hello, World!
Peek   :: Hello, World!
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Ljava.lang.String; field B.arr to [Ljava.lang.String;
        at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
        at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
        at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
        at java.lang.reflect.Field.set(Field.java:764)
        at A.main(A.java:14)

Why is this happening?? I tried to set again the accessible flag after the call to get but it doesn't help. I tried many other things that don't seem to help either...

Thanks for your help!


Edit: there is an element of answer in Using reflection to change static final File.separatorChar for unit testing? (see "Important update" by @Rogério).

Important update: the above solution does not work in all cases. If the field is made accessible and read through Reflection before it gets reset, an IllegalAccessException is thrown. It fails because the Reflection API creates internal FieldAccessor objects which are cached and reused (see the java.lang.reflect.Field#acquireFieldAccessor(boolean) implementation). Example test code which fails:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException

Sadly, it doesn't say anything about how to work around it... How do I "reset" the field?

Laurent Chabot
  • 468
  • 4
  • 9

1 Answers1

7

A very bad idea, indeed.

I'm not sure how deeply one should go into details when answering this question. But here's a short summary of what happens:

When you do the reflective Field#get call, the the call will internally (after a few security checks) be delegated to a sun.reflect.FieldAccessor. This is an internal interface that, as the name suggests, provides access to field values. The FieldAccessor instance that is used internally is created lazily, "cached" for later use, and even shared among multiple Field instances.

There are many different implementations of the FieldAccessor interface. These implementations are specialized for the various cases of normal Fields, static Fields, or private Fields that have been made accessible by calling setAccessible(true). For example, in your case, there is a UnsafeQualifiedStaticObjectFieldAccessorImpl involved, and the name already suggests that this is only one of a few dozen specializations.

Many of these FieldAccessor implementations store an internal state, which describe some properties of the field. For example, whether the field is "read only" or final (!).

The point is: The FieldAccessor that is created when you do the reflective Field#get call is the same that will later be used for the reflective Field#set call. But when the FieldAccessor is created, the field is still considered to be final. You are changing this only after you created the FieldAccessor.

So the easiest solution is to make sure that the final-status of the field is changed before you do this first reflective call. This way, the FieldAccessor that is created internally is one that operates on a "non-final" field:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class A {

    public static void main(String[] args) throws Exception {
        System.out.println("Before :: " + B.get());
        Field field = B.class.getDeclaredField("arr");
        field.setAccessible(true);

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        System.out.println("Peek   :: " + ((String[]) field.get(null))[0]);
        field.set(null, new String[] { "Good bye, World!" });
        System.out.println("After  :: " + B.get());
    }
}

class B {
    private static final String[] arr = new String[] { "Hello, World!" };

    public static String get() {
        return arr[0];
    }
}

I should not mention this. People will do this. But however:

Technically, it is also possible to brutallyHackYourWayThroughInternalClasses and modify the FieldAccessor that was created internally, so that it afterwards allows modifying the field, even though it initially was created for the final version of the field:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class A {

    public static void main(String[] args) throws Exception {
        System.out.println("Before :: " + B.get());
        Field field = B.class.getDeclaredField("arr");
        field.setAccessible(true);

        System.out.println("Peek   :: " + ((String[]) field.get(null))[0]);
        brutallyHackYourWayThroughInternalClasses(field);

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        field.set(null, new String[] { "Good bye, World!" });
        System.out.println("After  :: " + B.get());
    }

    private static void brutallyHackYourWayThroughInternalClasses(Field field)
        throws Exception
    {
        Field overrideFieldAccessorField = 
            Field.class.getDeclaredField("overrideFieldAccessor");
        overrideFieldAccessorField.setAccessible(true);
        Object overrideFieldAccessorValue = 
            overrideFieldAccessorField.get(field);

        Class<?> unsafeFieldAccessorImplClass = 
            Class.forName("sun.reflect.UnsafeFieldAccessorImpl");
        Field isFinalField = 
            unsafeFieldAccessorImplClass.getDeclaredField("isFinal");
        isFinalField.setAccessible(true);
        isFinalField.set(overrideFieldAccessorValue, false);

        Class<?> unsafeQualifiedStaticFieldAccessorImplClass = 
            Class.forName("sun.reflect.UnsafeQualifiedStaticFieldAccessorImpl");
        Field isReadOnlyField = 
            unsafeQualifiedStaticFieldAccessorImplClass.getDeclaredField(
                "isReadOnly");
        isReadOnlyField.setAccessible(true);
        isReadOnlyField.set(overrideFieldAccessorValue, false);
    }
}

class B {
    private static final String[] arr = new String[] { "Hello, World!" };

    public static String get() {
        return arr[0];
    }
}
Marco13
  • 53,703
  • 9
  • 80
  • 159