Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Follow up: instance control in Java without enum [closed]

Tags:

java

This is a follow up to this question. Is this solution watertight?

public final class Thing implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final Thing[] instances = new Thing[2];
    private static int nextOrdinal = 0;

    public static final Thing instance0 = new Thing("whatever0");
    public static final Thing instance1 = new Thing("whatever1");

    private transient final String someState;

    public String someMethod() {return someState;}

    private final int ordinal;

    private Thing(String someState) {
        this.someState = someState;
        this.ordinal = nextOrdinal++;
        instances[this.ordinal] = this;
    }

    private Object readResolve() throws ObjectStreamException {
        return instances[this.ordinal];
    }
}
like image 768
jjujuma Avatar asked May 22 '09 08:05

jjujuma


2 Answers

Is this solution watertight?

No. (Though it might be adequate, depending on how and where the code is used.)

In Item 77: For instance control, prefer enum types to readResolve (Effective Java, 2nd ed), Bloch demonstrates how an attacker could have a class like yours return any value. The attack relies on hand-crafted byte input and being able to run code on the target (so your code might be a security risk if used in sand-boxed environments, some RMI cases, etc.). I do not know if that is the only attack - it is the only one mentioned. The solution is to declare all fields transient, but then you have the issue of how to store the value.

You may be able to get round these problems using the serialization proxy pattern (Item 78 in the book - there's a reason every Java programmer who's read it recommends it).

public final class Thing implements Serializable {
  private static final long serialVersionUID = 1L;
  private static final Thing[] INSTANCES = new Thing[2];
  private static int NEXT_ORDINAL = 0;

  public static final Thing INSTANCE0 = new Thing(
      "whatever0");
  public static final Thing INSTANCE1 = new Thing(
      "whatever1");

  private transient final String someState;

  public String someMethod() {
    return someState;
  }

  private final int ordinal;

  private Thing(String someState) {
    this.someState = someState;
    ordinal = NEXT_ORDINAL++;
    INSTANCES[ordinal] = this;
  }

  private Object writeReplace() {
    return new ThingProxy(this);
  }

  private void readObject(ObjectInputStream stream)
      throws InvalidObjectException {
    throw new InvalidObjectException("Proxy required");
  }

  private static class ThingProxy implements Serializable {
    private static final long serialVersionUID = 1L;
    private final int ordinal;

    private ThingProxy(Thing t) {
      ordinal = t.ordinal;
    }

    private Object readResolve()
        throws ObjectStreamException {
      return INSTANCES[ordinal];
    }

  }

}

Though, as with copying anything security related from the internet, caveat emptor. I am by no means an expert.

like image 153
McDowell Avatar answered Sep 22 '22 08:09

McDowell


this code still missing some interesting features of Enum:

  • missing "values()" as Iterator over all enum values
  • still missing Ranged Iterator
  • cannot be used in switch

Example

public final class Day implements Serializable {

    private static final long serialVersionUID = 1L;
    private static Day[] instances = new Day[7];
    public static final Day MONDAY = new Day("Monday");
    public static final Day TUESDAY = new Day("Tuesday");
    public static final Day WEDNESDAY = new Day("Wednesday");
    public static final Day THURSDAY = new Day("Thursday");
    public static final Day FRIDAY = new Day("Friday");
    public static final Day SATURDAY = new Day("Saturday");
    public static final Day SUNDAY = new Day("Sunday");
    private static int nextOrdinal = 0;
    private transient final String name;
    private final int ordinal;

    private Day(String aName) {
        this.name = aName;
        this.ordinal = nextOrdinal++;
        instances[this.ordinal] = this;
    }

    private Object readResolve() throws ObjectStreamException {
        return instances[this.ordinal];
    }

    public String toString() {
        return name;
    }

    public boolean equals(Object obj) {
        return obj instanceof Day && ((Day) obj).ordinal == ordinal;
    }

    public int hashCode() {
        return name.hashCode();
    }

    public static Iterator values() {
        return new Iterator() {

            private int i = 0;

            public boolean hasNext() {
                return i < instances.length;
            }

            public Object next() {
                return instances[i++];
            }

            public void remove() {
                throw new UnsupportedOperationException("Not supported.");
            }
        };
    }

    public static Iterator range(final Day from, final Day to) {
        return new Iterator() {

            private int i = from.ordinal;

            public boolean hasNext() {
                return i <= to.ordinal;
            }

            public Object next() {
                return instances[i++];
            }

            public void remove() {
                throw new UnsupportedOperationException("Not supported.");
            }
        };
    }

    public static void main(String[] args) {
        Iterator week = Day.values();

        while (week.hasNext()) {
            System.out.println(week.next());
        }

        Iterator weekEnd = Day.range(SATURDAY, SUNDAY);

        while (weekEnd.hasNext()) {
            System.out.println(weekEnd.next());
        }
    }
}
like image 1
dfa Avatar answered Sep 22 '22 08:09

dfa