The problem with encapsulating C.val + autoboxing
Remi Forax
forax at univ-mlv.fr
Mon Jul 25 20:33:29 UTC 2022
One of the idea of encapsulating C.val is that even with a value class C with no good default, accessing C.val is useful.
That's true but a unfortunate consequence is that it makes leaking T in a generic code a security issue.
It is in fact more secure to use two classes C and CFlat, one with no good default and the other which allow the default value, when using with generics.
Here is an example, let say we have a value class Month (1 to 12) and an identity class Agenda that contains several months.
WE can declare Month like this, with a package-private value companion.
value record Month(int value) {
/*package-private*/ companion val;
public Month {
if (value < 1 || value > 12) throw new IAE();
}
}
So we can flatten the Month when stored in a list
class Agenda {
private final MyList<Month.val> months = new MyList<>();
public void add(Hour hour) {
Objects.requireNonNull(hour);
months.add(hour);
}
public Hour getFirst() {
return months.isEmpty()? null: months.getFirst();
}
}
Is this code safe ? The trouble is that it depends on the implementation of MyList, by example with
class MyList<E> {
private E[] array = new E[16];
private int size;
public boolean isEmpty() { return size == 0; }
public void add(E element) {
array[size++] = element;
}
public E getFirst() {
// Objects.checkIndex(0, size);
return array[0];
}
}
MyList.getFirst() leaks E.default, so the implementation of Agenda is not safe.
Using the encapsulation to hide C.val is only safe if the generics code never leak E.default.
Weirdly, if i use different classes to represent C and C.val, i do not have that issue.
class Agenda {
value record MonthFlat(int value) {
public companion val;
}
private final MyList<MonthFlat> months = new MyList<>();
public void add(Hour hour) {
Objects.requireNonNull(hour);
months.add(new MonthFlat(hour.value()));
}
public Hour getFirst() {
return months.isEmpty()? null: new Month(months.getFirst().value);
}
}
because unlike the autoboxing conversion between Month.val to Month, the conversion from MonthFlat to Month does not bypass the constructor.
Rémi
More information about the valhalla-spec-observers
mailing list