Possible Heap Pollution via Varargs Parameter

Possible heap pollution via varargs parameter

Heap pollution is a technical term. It refers to references which have a type that is not a supertype of the object they point to.

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

This can lead to "unexplainable" ClassCastExceptions.

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0);

@SafeVarargs does not prevent this at all. However, there are methods which probably will not pollute the heap, the compiler just can't prove it. Previously, callers of such APIs would get annoying warnings that were completely pointless but had to be suppressed at every call site. Now the API author can suppress it once at the declaration site.

However, if the method actually is not safe, users will no longer be warned.

Potential heap pollution via varargs parameter for EnumE... why?

There is a warning for the varargs method because varargs methods can cause implicit array creation at the calling site, whereas the version that takes an array parameter doesn't. The way that varargs works is that it causes the compiler to create an array at the call site filled with the variable arguments, which is then passed to the method as a single array parameter. The parameter would have type E[], so the array created should be an E[].

First, in the call site in your example, you are not using the variable arguments functionality at all. You are passing the array of variable arguments directly. So there is no implicit array creation in this case.

Even if you had used the variable arguments functionality, e.g. with someMethod(Foo.A);, the implicit array creation would be creating an array with a reifiable type, i.e. the variable argument type is known at the call site to be Foo which is a concrete type known at compile-time, so array creation is fine.

The problem is only if the variable argument type at the call site is also a generic type or type parameter. For example, something like:

public static <E extends Enum<E>> void foo(E obj) {
someMethod(obj);
}

Then the compiler would need to create an array of this generic type or type parameter (it needs to create an E[]), but as you know generic array creation is not allowed in Java. It would instead create an array with the component type being the erasure of the generic type (in this example it would be Enum), so it would pass an array of the wrong type (in this example, the passed array should be an E[], but the array's actual runtime class would be Enum[] which is not a subtype of E[]).

This potential wrong type of array is not always a problem. Most of the time, a varargs method will simply iterate over the array and get elements out of it. In this case, the runtime class of the array is irrelevant; all that matters is that the elements are type E (which indeed they are). If this is the case, you can declare your method @SafeVarargs. However, if your method actually uses the runtime class of the passed array (for example, if you return the varargs array as type E[], or you use something like Arrays.copyOf() to create an array with the same runtime class), then a wrong runtime class of the array will lead to problems and you cannot use @SafeVarargs.

Your example is a bit weird because, well, you are not even using the fact that the elements are of type E, let alone the fact that the array is E[]. So you could use @SafeVarargs, and not only that, you could simply declare the array as taking Object[] in the first place:

private static void someMethod(Object... objects)

Type safety: Potential heap pollution via varargs parameter subtrees

How can I check that my code actually safe?

It's safe if the visitors only rely on the fact that the elements of subtrees are Tree<E>, and do not rely on the fact that subtrees is Tree<E>[]. If that is the case, then you should annotate the visit method with @SafeVarargs.

java warning: Varargs method could cause heap pollution from non-reifiable varargs parameter

None of the answers I've seen on this question seem to me to be satisfactory so I thought I'd take a stab at it.

Here's the way I see it:

  1. @SafeVarargs
  • Suppresses the warning: [unchecked] Possible heap pollution from parameterized vararg type Foo.
  • Is part of the method's contract, hence why the annotation has runtime retention.
  • Is a promise to the caller of the method that the method will not mess up the heap using the generic varargs argument.

  1. @SuppressWarnings("varargs")
  • Suppresses the warning: [varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter bar.
  • Is a cure for problems occurring within the method code, not on the method's contract, hence why the annotation only has source code retention.
  • Tells the compiler that it doesn't need to worry about a callee method called by the method code messing up the heap using the array resulting from the non-reifiable varargs parameter.

So if I take the following simple variation on OP's original code:

class Foo {
static <T> void bar(final T... barArgs) {
baz(barArgs);
}
static <T> void baz(final T[] bazArgs) { }
}

The output of $ javac -Xlint:all Foo.java using the Java 9.0.1 compiler is:

Foo.java:2: warning: [unchecked] Possible heap pollution from parameterized vararg type T
static <T> void bar(final T... barArgs) {
^
where T is a type-variable:
T extends Object declared in method <T>bar(T...)
1 warning

I can make that warning go away by tagging bar() as @SafeVarargs. This both makes the warning go away and, by adding varargs safety to the method contract, makes sure that anyone who calls bar will not have to suppress any varargs warnings.

However, it also makes the Java compiler look more carefully at the method code itself - I guess in order to verify the easy cases where bar() might be violating the contract I just made with @SafeVarargs. And it sees that bar() invokes baz() passing in barArgs and figures since baz() takes an Object[] due to type erasure, baz() could mess up the heap, thus causing bar() to do it transitively.

So I need to also add @SuppressWarnings("varargs") to bar() to make that warning about bar()'s code go away.

Varargs polluting the heap

Am I right that it's ok to suppress the warnings in this case?

Here the Varargs are safely used as :

  • K and V argument types are bounded at compile time on the generics specified as you instantiate MapMaker :

    final Map<Integer, String> mapIntStr = new MapMaker<Integer, String>()

  • You don't perform unsafe casts with the vargs variables and you don't assign them to a declared Object[] array to value it with incompatible objects that could cause java.lang.ArrayStoreException (that is not a Heap pollution but that is also problematic).

  • You use an array of generic and not an array of a generic collection. Heap pollution is less common in this configuration as you don't have a collection that may contain incompatible elements that could trigger a ClassCastException.

So yes @SafeVarargs makes sense.


Why do I need two annotations for the keys method?

Annotating the keys() method with these two methods :

@SafeVarargs
@SuppressWarnings("varargs")

should not be required.

@SafeVarargs is indeed enough as java.lang.SafeVarargs javadoc states :

Applying this annotation to a method or constructor suppresses
unchecked warnings
about a non-reifiable variable arity (vararg) type
and suppresses unchecked warnings about parameterized array creation
at call sites.

You should probably check your IDE configuration about it.

I tested with javac and no warning is emitted by using only the @SafeVarargs annotation.

Does this cause heap pollution with varargs?

The warning about generic varargs is related to the dangers of generic arrays. Theoretically the method could abuse array covariance with the passed in array to cause heap pollution, for example:

Class<?>[] eventTypesWithWidenedType = eventTypes;
eventTypesWithWidenedType[0] = String.class;
Class<? extends Event> eventType = eventTypes[0]; // liar!

But it's fine as long as the method implementation doesn't do anything silly like that. Some basic precautions would be:

  • Don't do any assignment to eventTypes.
  • Don't return or otherwise expose eventTypes outside the method.

With Java 7, you could annotate the method with @SafeVarargs, which basically promises the compiler that generic arrays are okay (meaning it's no longer on the caller to suppress the warning).

heap pollution for simple parameter

Varargs in Java is a syntatic sugar. T... xs is the same as T[] xs. Therefore actually you have a function with parametrized parameter (the array).

Back to your question. Let's consider the situation, when you pass List<String> as generic type to the method. Then you have an array of elements of List<String> as an argument to your method. Two important issues arise because of this and lead to possible flaw in the code.

  1. Arrays are covariant and it means that the compilator will allow this assignment: Object[] arr = xs
  2. Because of generic erasure during compile time the array contains raw List elements and, unfortunately, there is no way in Java to guarantee, that the element you put in the array is exactly List<String>. So if you put List<Integer> java.lang.ArrayStoreException will not be thrown at runtime.

This alltogether leads to a situation when you may produce heap polution. Please, see the following example:

public static <T> List<T> list(T... xs) {
final List<T> lst = new ArrayList<T>();
for (final T x : xs) {
lst.add(x);
}
Object[] arr = xs; //arrays are covariant, we can do this
arr[0] = Arrays.asList(4); //<--------heap pollution
return lst;
}
public static void main(String[] args) {
List[] arr = { Arrays.asList("one"), Arrays.asList("two"), Arrays.asList("three") };
List<List<String>> l = list(arr);
for (List list : arr) {
System.out.println(list.get(0));
}
}

Output:

4
two
three

Here is a really good one explanation on the topic:
http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ300

Official Java Guide:
https://docs.oracle.com/javase/8/docs/technotes/guides/language/non-reifiable-varargs.html



Related Topics



Leave a reply



Submit