19

I am using IntelliJ IDEA with javac on JDK 1.8. I have the following code:

class Test<T extends Throwable>
{
    @SafeVarargs
    final void varargsMethod( Collection<T>... varargs )
    {
        arrayMethod( varargs );
    }

    void arrayMethod( Collection<T>[] args )
    {
    }
}

IntelliJ IDEA does not highlight anything in the above code as a warning. However, when compiling, the following line appears in the "Make" tab of the "Messages" view:

Warning:(L, C) java: Varargs method could cause heap pollution from non-reifiable varargs parameter varargs

Note #1: I have already specified @SafeVarargs.

Note #2: Warning:(L,C) points to varargs being passed as an argument to arrayMethod()

Assuming that I know what I am doing, and supposing that I am pretty sure that there will be no heap pollution or that I promise that I will not call this method in some funky way which might result in heap pollution, what do I need to do to suppress this warning message?

NOTE: There is a multitude of questions on stackoverflow regarding varargs methods, but it appears that there is none that addresses this specific problem. As a matter of fact, the entire interwebz appear to be rather poor in answers to this particular question.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142

5 Answers5

20

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.

Flow
  • 23,572
  • 15
  • 99
  • 156
0xbe5077ed
  • 4,565
  • 6
  • 35
  • 77
  • It's enough to decorate bar with @SafeVarargs and make barArgs final – grebulon Mar 28 '22 at 10:58
  • Unfortunately Java 17 compiling with `-Xlint:all` is still producing "Varargs method could cause heap pollution from non-reifiable varargs parameter …" even though I have `@SafeVarargs` on the method. Eclipse 2022-09 tells me that `@SuppressWarnings("varargs")` is unsupported. Any ideas? – Garret Wilson Sep 18 '22 at 20:56
4

An additional (and quite superfluous-looking) @SuppressWarnings( "varargs" ) is needed to suppress the warning, as follows:

@SafeVarargs
@SuppressWarnings( "varargs" )
final void varargsMethod( Collection<T>... varargs )
{
    arrayMethod( varargs );
}
Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
4

In fact you should not write your code in this way. Consider the following example:

import java.util.*;

class Test<T extends Throwable>
{
    @SafeVarargs
    @SuppressWarnings("varargs")
    final void varargsMethod( Collection<T>... varargs )
    {
        arrayMethod( varargs );
    }

    void arrayMethod( Collection<T>[] args )
    {
        Object[] array = args;
        array[1] = new Integer(1);
        //
        //ArrayList<Integer> list = new ArrayList<>();
        //list.add(new Integer(1));
        //array[1] = list;
    }

    public static void main(String[] args)
    {
        ArrayList<Exception> list1 = new ArrayList<>();
        ArrayList<Exception> list2 = new ArrayList<>();
        (new Test<Exception>()).varargsMethod(list1, list2);
    }
}

If you run the code, you will see an ArrayStoreException because you put an Integer into a Collection<T> array.

However, if you replace array[1] = new Integer(1); with the three comment lines (i.e. to put an ArrayList<Integer> into the array), due to type erasure, no exception is thrown and no compilation error occurs.

You want to have a Collection<Exception> array, but now it contains a ArrayList<Integer>. This is quite dangerous as you won't realise there is a problem.

tonychow0929
  • 458
  • 4
  • 10
  • Tony, thanks for your thoughtful comment and the effort you put into providing working code based on my code which shows the danger of this construct, despite the fact that it is somewhat off-topic. Of course I do not write code exactly like that, I always immediately wrap these arrays in unmodifiable collections, and the array elements in turn are other unmodifiable classes instead of `Collection`, so nothing really can go wrong. I just used `Collection` here for convenience, since it already exists in `java.util` so I did not have to provide an additional class in the sample code. – Mike Nakis Mar 07 '15 at 12:35
  • 2
    This depends on what `@SafeVarargs` is supposed to mean. My feeling is that `@SafeVarargs` is a promise that the method doesn't do anything unsafe with the varargs parameter, and that should *include* what methods that you pass this parameter to do with it. Under this interpretation, if `arrayMethod` does something unsafe with it as in your example, then you should not say `@SafeVarargs` for `varargsMethod`. – newacct May 11 '15 at 22:45
  • Also, what part of the JLS specifies the warning in this case? – newacct May 11 '15 at 22:47
  • I just want to say, @SafeVarargs should not be used lightly, or subtle bugs may arise which results in arbitrary behaviour. – tonychow0929 May 12 '15 at 02:26
  • @tony200910041: I don't see how unchecked conversions is related. There is no unchecked conversion in `varargsMethod`, and the warning you are suppressing is `varargs`, not `unchecked`. – newacct May 12 '15 at 18:48
  • @newacct sorry that I misunderstood what you mean. Would you want some more details explained here? https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.4.7 – tonychow0929 May 14 '15 at 03:49
  • @tony200910041: I am trying to determine whether this warning is a compiler bug or not. So I try to see if anywhere in the JLS specifies that if you're inside a varargs method and you pass the varargs parameter into a method that takes an array, that there should be a warning. I am unable to find any place that specifies this. – newacct May 14 '15 at 07:09
  • This example doesn't illustrate why creating arrays of non-reifiable types is dangerous. It only illustrates that you shouldn't attempt to store elements of type `X` into an array of type `Y[]` (where `X` is not convertible to `Y`) through a reference of type `Object[]`. Consider: `Object[] a = new String[2]; a[1] = Integer.valueOf(42);`. Clearly this will fail and for exactly the same reason as the example fails. – Matt Whitlock Sep 25 '17 at 19:29
1

Assuming that I know what I am doing

I refuse to assume this since this seems incredibly wrong.

The situation you're running into here is that, because generics are not reifiable, you're declaring a generic array, which is generally frowned upon. Generics and arrays don't mix very well, given that an array is covariant (String[] is an Object[] in the same way that a String is an Object), whereas generics are invariant (List<String> is not a List<Object>, even though a String is an Object).

If you want a collection of collections...just pass one. It's safer than mingling arrays and generics.

final void varargsMethod(Collection<<Collection<? super T>> collections) { }
Makoto
  • 104,088
  • 27
  • 192
  • 230
-3

This might explain the reason why. I just copied it from Effective Java 2nd Edition, Item 25. Hopefully, it can help.

The prohibition on generic array creation can be annoying. It means, for example, that it’s not generally possible for a generic type to return an array of its element type (but see Item 29 for a partial solution). It also means that you can get confusing warnings when using varargs methods (Item 42) in combination with generic types. This is because every time you invoke a varargs method, an array is created to hold the varargs parameters. If the element type of this array is not reifiable, you get a warning. There is little you can do about these warnings other than to suppress them (Item 24), and to avoid mixing generics and varargs in your APIs.

Kin Cheung
  • 870
  • 10
  • 20
  • 1
    But this has nothing to do with the question. Generic array creation on invoking a varargs method happens when `varargsMethod()` is called. (That's why there's a `@SafeVarargs`.) But the warning in the question happens when `arrayMethod()`, a *non-varargs* method is called. – newacct Jun 12 '15 at 18:12
  • @newacct basically, it is illegal to "create" an array of a generic type and the method argument, varargs, in varargsMethod will cause a creation of an array of Collection, therefore it raises type safety warning from the compiler. On the other hand, arrayMethod() that takes an array of a generic type doesn't cause any generics array creation, it is therefore legal and doesn't cause any warning at all. – Kin Cheung Jun 15 '15 at 03:18
  • @KinCheung: But that's the thing. The warning at question here *is* on the call to `arrayMethod()`, not on the call to `varargsMethod()` – newacct Jun 15 '15 at 15:10
  • @newacct: right, I got what you meant now. After I have put SafeVarargs above varargsMethod(), I don't see anymore warning. I have tried it in Eclipse and 1.8 javac using the command line. So, it is unlikely related to the compiler about the warning on the call to arrayMethod(). – Kin Cheung Jun 16 '15 at 03:45