4

In Java, it turns out that field accessors get cached, and using accessors has side-effects. For example:

class A {
    private static final int FOO = 5;
}

Field f = A.class.getDeclaredField("FOO");
f.setAccessible(true);
f.getInt(null); // succeeds

Field mf = Field.class.getDeclaredField("modifiers" );
mf.setAccessible(true);

f = A.class.getDeclaredField("FOO");
f.setAccessible(true);
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setInt(null, 6); // fails

whereas

class A {
    private static final int FOO = 5;
}

Field mf = Field.class.getDeclaredField("modifiers" );
mf.setAccessible(true);

f = A.class.getDeclaredField("FOO");
f.setAccessible(true);
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setInt(null, 6); // succeeds

Here's the relevant bit of the stack trace for the failure:

java.lang.IllegalAccessException: Can not set static final int field A.FOO to (int)6
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:100)
    at sun.reflect.UnsafeQualifiedStaticIntegerFieldAccessorImpl.setInt(UnsafeQualifiedStaticIntegerFieldAccessorImpl.java:129)
    at java.lang.reflect.Field.setInt(Field.java:949)

These two reflective accesses are of course happening in very different parts of my code base, and I don't really want to change the first to fix the second. Is there any way to change the second reflective access to ensure it succeeds in both cases?

I tried looking at the Field object, and it doesn't have any methods that seem like they would help. In the debugger, I noticed overrideFieldAccessor is set on the second Field returned in the first example and doesn't see the changes to the modifiers. I'm not sure what to do about it, though.

If it makes a difference, I'm using openjdk-8.

Ellis Michael
  • 962
  • 1
  • 8
  • 14

1 Answers1

11

If you want the modifier hack (don't forget it is a total hack) to work, you need to change the modifiers private field before the first time you access the field.

So, before you do f.getInt(null);, you need to do:

mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);

The reason is that only one internal FieldAccessor object is created for each field of a class (*), no matter how many different actual java.lang.reflect.Field objects you have. And the check for the final modifier is done once when it constructs the FieldAccessor implementation in the UnsafeFieldAccessorFactory.

When it is determined you can't access final static fields (because, the setAccessible override doesn't works but non-static final fields, but not for static final fields), it will keep failing for every subsequent reflection, even through a different Field object, because it keeps using the same FieldAccessor.

(*) barring synchronization issues; as the source code for Field mentions in a comment:

// NOTE that there is no synchronization used here. It is correct (though not efficient) to generate more than one FieldAccessor for a given Field.

Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • as a side note, as I was testing the above code, I used the "working" version and then immediately printed FOO to see the result. The bytecode was probably optimised to output a constant 5 because it knew the value was final and "cant" be changed. – slipperyseal Jan 11 '18 at 01:41
  • @slipperyseal True, constants in `static final` fields are inlined. It is easier to observe effects if you make it a non-constant, for example like this: `private static final int FOO = new Integer(5);` – Erwin Bolwidt Jan 11 '18 at 01:48
  • I was hoping there would be a better way, but that makes sense. – Ellis Michael Jan 11 '18 at 02:08
  • 2
    You may use `static final int FOO = Math.abs(5);` to prevent compile-time optimization without creating an `Integer` object. Or `static final int FOO; static { FOO = 5; }`. But keep in mind that this does not prevent runtime optimizations. In principle, the JVM is allowed to aggressively optimize `static final` fields without caring for reflective manipulations that are not allowed by the JLS. I strongly discourage from doing that; there is no imaginable scenario without a cleaner and even simpler alternative. – Holger Jan 11 '18 at 07:13