0

Let's take this sample Java method:

public static <E> HashSet<E> createHashSet(final E... elements) {
  final HashSet<E> hashSet = new HashSet<E>(elements.length);
  java.util.Collections.addAll(hashSet, elements);
  return hashSet;
}

If I'm compiling with Java 17 using -Xlint:all, I get the following warning for the method signature:

Varargs method could cause heap pollution from non-reifiable varargs parameter iterators

Eclipse 2022-09 suggests using @SafeVarargs. And indeed Java's own java.util.Collections.addAll(…) itself uses @SafeVarargs! Yet when I add it to the method signature, javac gives me the same warning, only further down in the java.util.Collections.addAll(…) line.

The answers to java warning: Varargs method could cause heap pollution from non-reifiable varargs parameter suggest additionally using @SuppressWarnings("varargs") on the method for IntelliJ. And indeed when I use both @SafeVarargs and @SuppressWarnings("varargs") together, the lint warning goes away with javac. However Eclipse says this annotation value "varargs" is unsupported.

Eclipse also suggests using @SuppressWarnings("unchecked"). If I add only @SuppressWarnings("unchecked") to the method, then the warning goes away both in Eclipse and in javac with linting! So it would seem that a single @SuppressWarnings("unchecked") is the way to go.

But is it? https://stackoverflow.com/a/47949197 seems to indicate that the way to go for IntelliJ is @SafeVarargs and @SuppressWarnings("varargs") together, and https://stackoverflow.com/a/44675766 seems to agree in general. And the source code for Java uses @SafeVarargs.

Is Eclipse incorrect in forcing me to use @SuppressWarnings("unchecked") instead of @SafeVarargs and @SuppressWarnings("varargs") together? I have opened Eclipse Issue #453 to try to get more answers.

Note on duplicates

Note that java warning: Varargs method could cause heap pollution from non-reifiable varargs parameter does not seem to be a duplicate; it is about avoiding this warning on IntelliJ. The solution there does not work for Eclipse.

halfer
  • 19,824
  • 17
  • 99
  • 186
Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
  • 1
    According to https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/SafeVarargs.html Eclipse does it right. – howlger Oct 05 '22 at 08:09
  • Could you explain further? The `SafeVarArgs` API documentation you mention says that if a method is annotated with `@SafeVarArgs` but the body performs unsafe operations, the compiler may issue a further warning, but it doesn't say how to suppress those warnings. `javac` and others say to _add_ a `@SuppressWarnings("varargs")` annotation. Eclipse expects us to _remove_ `@SafeVarArgs` and _instead_ add `@SuppressWarnings("unchecked")`. But [javac Xlint](https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html) does list `varargs` in the docs. – Garret Wilson Oct 05 '22 at 12:55
  • For me both works fine in Eclipse, `@SafeVarargs` or `@SuppressWarnings("unchecked")`. `@SafeVarargs` Javadoc: _" Applying this annotation to a method [...] suppresses unchecked warnings"_. You said _" when I add it to the method signature, `javac` gives me the same warning, only further down in the `java.util.Collections.addAll(…)` line."_ which sound to me like the opposite of that. Does `javac` behave the same in all Java versions? – howlger Oct 05 '22 at 14:21
  • "For me both works fine in Eclipse, `@SafeVarargs` or `@SuppressWarnings("unchecked")`." Ah, I think what you might have missed in the description is that in Java 17 I'm compiling with `-Xlint:all`. That's when it turns out that `@SafeVarargs` is insufficient; it only applies to the _method parameters_, not things javac feels are unsafe it the method body. That's where the `@Suppresswarnings("varargs")` comes in, which Eclipse doesn't recognize. See the [Xlint keys](https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html). – Garret Wilson Oct 05 '22 at 14:25
  • See also [Eclipse Bug 344783](https://bugs.eclipse.org/bugs/show_bug.cgi?id=344783), which (10 years ago!) said the same thing I'm saying, but somehow fell between the cracks; as well as [Eclipse Issue #453](https://github.com/eclipse-jdt/eclipse.jdt.core/issues/453), which I just opened. I think the issue is simply that Eclipse needs to recognize `@SuppressWarnings("varargs")` for non-reifiable varargs usage within a method—just as Oracle does, as OpenJDK does, and IntellJ does (to my understanding). – Garret Wilson Oct 05 '22 at 14:29
  • If you want to reproduce this, use [this `pom.xml`](https://raw.githubusercontent.com/globalmentor/globalmentor-root/main/pom.xml) as your Maven POM parent, set `17` in your project POM, and compile the example code in the description using `-P lint` to turn on linting. (I don't know what Java <17 does.) – Garret Wilson Oct 05 '22 at 14:32
  • Why do you think Eclipse needs to recognize `@SuppressWarnings("varargs")` when the documentation says those are _unchecked_ warnings? Eclipse sticks to the Java specification and does not try to mimic `javac`. So please refer to the documentation which says that it must be `@SuppressWarnings("varargs")` and not `@SuppressWarnings("unchecked")`. Please provide a pull request for what you want. – howlger Oct 05 '22 at 15:57
  • I guess one of my biggest doubts is why Eclipse wants me to remove `@SafeVarargs` if I add `@SuppressWarnings("unchecked")`. Even the JDK uses `@SafeVarargs`! Eclipse doesn't seem to understand that there are two levels of varargs-related problems: one in the method parameters (the incoming arguments) (`@SafeVarargs)`, and another in an invocation within the method body (`@SuppressWarnings("varargs")`). Eclipse would have me use `@SuppressWarnings("unchecked")` and remove `@SafeVarargs` altogether. Saying that `javac` got it wrong is also unsatisfying (although it's certainly possible). – Garret Wilson Oct 05 '22 at 18:52

1 Answers1

1

As solution use @SafeVarargs @SuppressWarnings({"all", "varargs"}).

Detailed explanation:

Why is there a potential heap pollution warning here?

Your method has varargs parameters of a generic type (E) which means that the elements will be an array of type Object[] at runtime which can lead to heap pollution. Heap pollution can also happen when assigning a more general type array to a more specific type array, as in the following example, but when using varargs it is less obvious as the Object[] array is implicitly created. The Javadoc for @SafeVarargs gives an example where the semantic error can be spotted when looking closely, but the example given by Baeldung - Java @SafeVarargs Annotation the error is not obvious at all, isn't it? Hence the warning.

String[] strings= { "a", "b" };
Object[] objects= strings;
objects[0]= Integer.valueOf(23); // heap pollution

How to solve this warning?

Since the Java compiler cannot compute whether the varargs of generic types are used semantically correctly (see halting problem), it is your job to check this.

The varargs usage is safe if and only if:

  • No storing of objects of a wrong type in the implicitly created varargs array
  • The implicitly created varargs array does not escape out of the method

If the varargs of a generic type are not semantically correctly used, fix your code. Otherwise, add @SafeVarargs (in Java 5 and 6 @SuppressWarnings has to be added instead, since @SafeVarargs has been introduced later in Java 7). Please note, in contrast to @SafeVarargs (@Retention(RUNTIME)), @SuppressWarnings (@Retention(SOURCE)) will be lost when shipping your code as binary.

In your example the varargs of the generic type E are safe: you don't store anything in the implicitly created varargs array, nor does the implicitly created varargs array escape the method. So the right thing is to add @SafeVarargs to the method (since @SafeVarargs has been added to Java 7, but the TYPE_PARAMETER target has been added in Java 8, it cannot be added directly to the varargs parameter, but only to the method).

Why does javac gives a warning even with @SafeVarargs?

javac with -Xlint:varargs checks whether @SafeVarargs has been correctly added. In the following example javac -Xlint:varargs shows five warnings. The warning of the varargs escaping to another @SafeVarargs method (see 3), as in your case, does not seem correct to me.

/////////////////////////////////////////////////////////////////////////////////////////////////
// warning: [varargs] Redundant SafeVarargs annotation. Varargs element type String is reifiable.
@SafeVarargs 
static <E> void foo(String... elements1) { }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// warning: [varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter elements2
@SafeVarargs 
static <E> E[] foo(E[] elements1, int i, E... elements2) {
    Object[] objects1= elements1;
    objects1[0]= new Object();
    Object[] objects2= elements2; // 1
    objects2[0]= new Object();
    array(elements1);
    array(elements2); // 2
    safeVararg(elements1);
    safeVararg(elements2); // 3 false positive?
    return i > 0
           ? elements1
           : elements2; // 4
}

@SafeVarargs
static <E> void safeVararg(E... elements) { }

static <E> void array(E[] elements) { }

The checks and thus the @SuppressWarnings keys differ in javac and in the Eclipse compiler for Java (ecj). In Eclipse there seems to be no equivalent to javac's varargs check/@SuppressWarnings yet (no problems are shown for the above example in Eclipse 2022-09).

Unfortunately, javac ignores @SuppressWarnings("all") which seems a bug of javac. So @SuppressWarnings("all") cannot be used here as workaround, but @SafeVarargs @SuppressWarnings({"all", "varargs"}) makes both compilers happy.

See also: Eclipse JDT core issues #453 (reported by you)

howlger
  • 31,050
  • 11
  • 59
  • 99