How private are private fields after all?

Last week, I sent the following brain teaser:

public class Foo {
  private int secret = 47;
  public Foo() {
    new Hacker().hack(this);
  }
  public String tellSecret() {
    return String.valueOf(secret); 
  }
  public static void main(String[] args) {
    System.out.println(''How secret are private members? ..'');
    System.out.println(''Psst: '' + new Foo().tellSecret();
  }
}

Can you write a void hack(Object obj) method that would change foo’s private member variable, so that the output looked like this?

Psst: 74

A sharp developer came up with this solution:

class Hacker {
  public void hack (Object o) {
    try {
      Field f = o.getClass().getDeclaredField( ''secret'' );
      f.setAccessible( true );
      f.setInt( o, 74 );
    } catch ( Exception e ) {
      e.printStackTrace();  
    }
  }
}

Now there you have it. Just because a member is declared private doesn’t mean it cannot be changed from outside.

Not only to emphasize this but also because it was more convenient, up to now, Swixml was only able to map public members.

FYI:
Swixml is a small GUI generating engine for Java applications and applets. Graphical User Interfaces are described in XML documents that are parsed at runtime and rendered into javax.swing objects.

While parsing an XML layout descriptor, the SwingEngine instantiates all the Components and assembles the GUI tree. In case a tag was given an idattribute, the SwingEngine will also put a reference to the instantiated object into an idmap. After all this is done, the SwingEngine introspect the class of a provided client object, e.g.

new SwingEngine( this ).render( ''xml/helloworld.xml'' ).setVisible ( true );

.. here ”this” points to the client object.

If during this introspection member variables in the client’s class are found, whose names match those in the idmap, the SwingEngine maps the reference into the member variable.

This used to work only with public members. Since this Swixml 1.5 however, the updated mapping considers ALL member variables in the whole class structure. This means even members in a super class and its super class … are considered. BUT, to work consistent with serialization, members flagged as transient are not mapped automatically.

Mapping works recursively on a client object’s class tree like this:

private void mapMembers( Object obj, Class cls ) {
    if (obj != null && cls != null && !Object.class.equals( cls )) {
        Field[] flds = cls.getDeclaredFields();
        for (int i = 0; i < flds.length; i++) {
            Object widget = idmap.get( flds[i].getName() );
            if ( widget != null 
                && flds[i].getType().isAssignableFrom ( widget.getClass() )
                && !Modifier.isTransient( flds[i].getModifiers() )) {
                try {
                    boolean accessible = flds[i].isAccessible();
                    flds[i].setAccessible(true);
                    flds[i].set(obj, widget);
                    flds[i].setAccessible(accessible);
                } catch ( .. ) {
                }
            }
        }
        mapMembers( obj, cls.getSuperclass() );
    }
}

 

 

Leave a Reply