I have been looking at various answers and articles regarding heap pollution when using non-reifiable varargs parameters. All examples I have found use an Object[]
conversion followed by an array element write. The heap pollution only ever seems to occur in this manner, for example:
public void example(List<Integer>... lists) { // lists[0] = Collections.singletonList("A!"); Object[] objects = lists; objects[0] = Collections.singletonList("B!"); // (pollutes!) }
Some other articles (such as this SO answer) also appear to be incorrect or entirely misleading:
So now we have a
List<String>
that actually contains anInteger
, and it’s floating around, safely.
The code presented in the answer never modifies the lists themselves, just the temporary varargs array within the faultyMethod
scope. This runnable demo shows that neither of the two lists are ever polluted and both retain their original String
objects (contrary to the answer suggesting they are polluted with an Integer
).
I now have two resulting questions:
Why does the compiler need to warn us on every possible method when the vast majority of them will not be converting to
Object[]
– surely it would be better to only warn on the problematicObject[]
line itself? Perhaps there is a scenario where “possible heap pollution from parameterized vararg type” can occur without any explicitObject[]
conversion?Is the answer to the previous SO question incorrect or am I misinterpreting the statement?
Advertisement
Answer
Why does the compiler need to warn us on every possible method when the vast majority of them will not be converting to
You have a variable of type e.g. List<Integer>[]
here but it can actually contain something else. Yes, there was a choice to instead treat the lists
parameter as having type [TAINTED]{List<Integer>[]}
instead, and have this voodoo magic unexpressible type act like a plain ole List<Integer>[]
when e.g. accessing it with foo = lists[0]
, but have it emit warnings if you try to write to it with e.g. lists[0] = ...;
.
But java didn’t – that would have complicated the typing system, requiring the introduction for the compiler of the notion of a type that means: “It’s like an array of this type, unless you write to it, in which case you specifically need to produce warnings now whereas for other ways of obtaining this type, you don’t”.
Your human brain looks at the whole thing and reasons about the whole thing. Computer code is written to abstract and modularize and limit. Compiler limits expressions to just ‘they are of this type’, they don’t “get to” keep in the back of their mind: Oh, yeah, this is a List<Integer>[]
but it’s varargs-based so I need to doublecheck if the code attempts to corrupt heap with this thing or not.
Note that @SafeVarargs
is an attempt to mitigate some of this, you should look into it.
Is the answer to the previous SO question incorrect or am I misinterpreting the statement?
yes, that 14-point, accepted answer is indeed utter tripe (That’s not sarcasm). I have no idea why it’s rated like this. It makes no sense.
The primary inherent danger in genericsed varargs is if you take the array itself and do anything with it other than use-it-and-lose-it: If you pass that array on to another method*, or store it in a field, that is a potential disaster waiting to happen. If you just read from it or loop through it, it doesn’t matter.
*) Of course, if the method(s) you pass the array to themselves recursively adhere to the rule of I either [A] read from it, and/or [B] loop through it, and/or [C] methods that fit this definition recursively, then there is no problem. As long as that array doesn’t ever get stored in a field or gets written to, it just doesn’t matter. The answer you linked to doesn’t write/store, and therefore makes no sense.