This post originated from an RSS feed registered with Java Buzz
by Vlad Roubtsov.
Original Post: Yes, Java fields can be overloaded too
Feed Title: Java Curiosity Shop
Feed URL: http://www.blog-city.com/bc/
Feed Description: Java esoterics: things from "don't try this at home" category
A short while ago I needed to add a new method to my .class parsing lib I have been tinkering with for a while. The new method was to lookup a field descriptor from a class definition based on the field name. As soon as I tried to write down the method signature and return type a thought occurred to me:
does the name string uniquely specify a class field within its declaring class?
For methods the answer is obviously no. As we all know, Java methods can be overloaded (both within the declaring class and across class boundaries). The unique identifier for a method is really a tuple (name, signature). What about Java fields?
I was curious enough to dig up the relevant section in the class format specification. The situation turns out to be quite similar to methods, although it can never be exploited at the syntax level. Section 4.5 of the JVM specification says:
No two fields in one class file may have the same name and descriptor
where the field descriptor is the standard type descriptor specified in section 4.3.2 (so, for a long field the descriptor would be J and so on).
The specification thus does not forbid a class from having more than one field with the same name as long as such fields differ in types. The specification does not explicitly mention this possibility either so I put together a quick test using a Main test driver class (compiled normally) and another class X (coded in Java assembly, so I show the javap dump instead):
import java.lang.reflect.Field;
public class Main
{
public static void main (final String [] args)
throws Exception
{
Field [] fields = X.class.getFields ();
for (int f = 0; f < fields.length; ++ f)
{
System.out.println (fields [f] + " = " + fields [f].get (null));
}
System.out.print ("getting a field by name: ");
Field x = X.class.getField ("s_x"); // which field does this get?
System.out.println (x);
}
} // end of class
public class X extends java.lang.Object {
public static int s_x;
public static long s_x;
static {};
public X();
}
Method static {}
0 iconst_1
1 putstatic #25 <Field int s_x>
4 ldc2_w #17 <Long 2>
7 putstatic #24 <Field long s_x>
10 return
Method X()
0 aload_0
1 invokespecial #19 <Method java.lang.Object()>
4 return
When I run Main in Suns JVM 1.4.1, I get this:
public static int X.s_x = 1
public static long X.s_x = 2
getting a field by name: public static int X.s_x
So, my X class definition was legal! This is kind of amusing. java.lang.reflect.* API is obviously not prepared to handle all possibilities for a valid class: Class.getField(String) does not take a fieldType Class parameter to support proper field lookup by analogy with getMethod(). Note that this is different from field hiding (I could create a class Y that extends X and declares public static int s_x again: this is perfectly ordinary and the documentation for getField() lookup algorithm disambiguates this situation).
In the above case getField(s_x) returned the first field seen in the array returned by getFields(), but this fact cannot be relied on (in fact, the ordering of field descriptors as returned by getFields() is documented to be unspecified). In Suns JVMs 1.2.2 and 1.3.1 the results were different:
public static int X.s_x = 1
public static long X.s_x = 2
getting a field by name: public static long X.s_x
This is just one of those Java curiosities that are possible at bytecode level and not possible at the Java syntax level. Granted, I couldnt think of any useful way to utilize this field overloading capability at first. But then it dawned on me that it would work well for obfuscation. Given a class, you could rename all of its fields using namespaces that are partitioned by all unique field types found in the class (obviously, along with other things a decent obfuscator would do). Straightforward decompilation of my X.class results in Java source that cannot be recompiled (without field name mangling). Here is the output from jad:
public class X
{
public X()
{
}
public static int s_x = 1;
public static long s_x = 2L;
}
It is quite possible that commercial obfuscators already exploit field overloading. I might check out some of them if I stay curious enough.