Implications for Non-Reifiable Variable Arity Parameter – Generics

Implications for Non-Reifiable Variable Arity Parameter

Because variable arity parameters are treated as arrays, generics have implications for non-reifiable variable arity parameters (T … varargs). Most of the workarounds for arrays are not applicable, as array creation is implicit for variable arity parameters. In a method declaration with a non-reifiable variable arity parameter, the compiler flags a warning about possible heap pollution from using a non-reifiable variable arity type. Heap pollution occurs when a reference of a parameterized type refers to an object that is not of the parameterized type. This can only occur when both the compiler and the JVM cannot guarantee the validity of an operation involving parameterized types. The compiler issues a warning about potential heap pollution, and the JVM normally throws an appropriate exception.

The method asStack() below has a variable arity parameter at (1) whose type is a non-reifiable type T. The method pushes the specified elements on to the specified stack. The compiler issues a possible heap pollution warning at (1).

Click here to view code image

public static <T> void
  asStack(MyStack<T> stack, T…elements) { // (1) Possible heap pollution warning
  for (T element : elements) { stack.push(element); }
}

In a method call, implicit creation of a generic array with the variable arity parameter results in an unchecked generic array creation warning—and type-safety is no longer guaranteed. The method above is called by the client code below at (4). The idea is to initialize a stack of stacks of Integer with a stack of Integer. An implicit generic array (new MyStack[] { intStack }) is created by the compiler, which is passed in the method call at (4). The compiler also issues an unchecked array creation warning, but the code compiles and runs without any problems.

Click here to view code image

/* Client code */
// (2) Create a stack of stacks of Integer:
MyStack<MyStack<Integer>> stackOfIntStacks = new MyStack<>();
// (3) Create a stack of Integer:
MyStack<Integer> intStack = new MyStack<>();
intStack.push(2019); intStack.push(2020);
// Initializes the stack of stacks with the stack of Integer.
MyStack.asStack(stackOfIntStacks, intStack); // (4) Unchecked array creation!
intStack = stackOfIntStacks.pop();      // (5) Pop the stack of stacks of Integer.
int tos = intStack.pop();               // (6) Pop the stack of Integer.
assert tos == 2020;

The implicit array passed as an argument is available as an array of a non-reifiable type in the body of the method asStack(). The integrity of this array can be compromised by making the array store check report a false positive at runtime—that is, succeed when the store operation should normally fail, thereby resulting in heap pollution. This is demonstrated by the method declaration below, in the assignment statement at (1a), where the contents of the elements array are changed before they are copied to the specified stack. The compiler issues a possible heap pollution warning at (1) and an unchecked cast warning at (1a).

Click here to view code image

public static <T> void asStackMalicious(
         MyStack<T> stack, T… elements) { // (1) Possible heap pollution warning
  // Compromise the elements array:
  MyStack<Double> doubleStack = new MyStack<>();
  doubleStack.push(20.20);
  elements[0] = (T) doubleStack;            // (1a) Unchecked cast warning!
  // Copy from elements array:
  for (T element : elements) { stack.push(element); }
}

A partial erasure for the method asStackMalicious() is shown below.

Click here to view code image

public static void asStackMalicious(MyStack stack, Object…elements) {
  // Compromise the elements array:
  MyStack doubleStack = new MyStack();
  doubleStack.push(Double.valueOf(20.20));
  elements[0] = (Object) doubleStack;                      // (1b)
  for (Object element : elements) { stack.push(element); } // (1c)

}

Note that the cast at (1b) succeeds for any object at runtime, as any object can be cast to Object. The assignment at (1b) succeeds if the array store check succeeds.

If we now call the method asStackMalicious(), instead of the method asStack() at (4) in the client code above, the code compiles with a generic array creation warning as before.

Click here to view code image

MyStack.asStackMalicious(stackOfIntStacks, intStack); // (4′) Unchecked warning!

At runtime, the reference elements in the method asStackMalicious() refers to the implicit array created in the call—that is, new MyStack[] { intStack }. The signature of the method call at runtime is equivalent to:

Click here to view code image

asStackMalicious(MyStack, MyStack[])

At runtime, the actual type of the stack parameter is MyStack and the actual type of the elements array parameter is MyStack[]. The element type of a stack has been erased. The references doubleStack and elements[0] both have the runtime type MyStack. When the code is run, the array store check succeeds at (1b), and element[0] now refers to a stack of Double. In the loop at (1c), this stack of Double is pushed on the stack of stacks of Integer referred to by the formal parameter stack. Heap pollution is now a fact.

After return from the call at (4) in the client code, the assignment at (5) also succeeds, as this is an assignment of a MyStack to a reference of the same type after erasure. The reference intStack now refers to a stack of Double. The error is only discovered after a ClassCastException is thrown at (6) because the Double that is popped from the stack of Integer cannot be converted and assigned to an Integer.

The general rule is to avoid the variable arity parameter in methods where the parameter is of a non-reifiable type. No matter what, it is the responsibility of the method declaration to ensure that the non-reifiable variable arity parameter is handled in a type-safe manner, and then to use one of the two annotations below to suppress unchecked warnings:

Click here to view code image

@SuppressWarnings(“unchecked”)                      // (1)
public static <T> void asStack(MyStack<T> stack, T…elements) { … }
@SafeVarargs                                        // (2)
public static <T> void asStack(MyStack<T> stack, T…elements) { … }

The method asStack() handles the non-reifiable variable arity parameter elements in a type-safe way, so suppressing the unchecked warning is justified. The annotation at (1) suppresses all unchecked warnings in the method declaration, but it does not suppress the unchecked generic array creation warning at the call sites. The annotation at (2) suppresses all unchecked warnings in the method declaration, as well as the unchecked generic array creation warning at the call sites. However, the @SafeVarargs annotation (§25.5, p. 1582) is only applicable to a variable arity method or constructor, and the method cannot be overridden—that is, the method must be either static, private, or final.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *