Possível poluição de pilha via parâmetro varargs

433

Entendo que isso ocorra com o Java 7 ao usar varargs com um tipo genérico;

Mas minha pergunta é ..

O que exatamente o Eclipse quer dizer quando diz "seu uso pode poluir a pilha?"

E

Como a nova @SafeVarargsanotação evita isso?

hertzsprung
fonte
Estou vendo isso no meu editor:Possible heap pollution from parameterized vararg type
Alexander Mills

Respostas:

252

Poluição por pilha é um termo técnico. Refere-se a referências que possuem um tipo que não é um supertipo do objeto para o qual apontam.

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

Isso pode levar a "inexplicáveis" ClassCastException.

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

@SafeVarargsnão impede isso. No entanto, existem métodos que provavelmente não poluirão a pilha, o compilador simplesmente não pode provar isso. Anteriormente, os chamadores dessas APIs recebiam avisos irritantes que eram completamente inúteis, mas precisavam ser suprimidos em todos os sites de chamadas. Agora, o autor da API pode suprimi-lo uma vez no site da declaração.

No entanto, se o método realmente não for seguro, os usuários não serão mais avisados.

Ben Schulz
fonte
2
Então, estamos dizendo que o heap está poluído porque contém referências cujos tipos não são o que podemos esperar? (Listar <A> vs Listar <B> no seu exemplo)
hertzsprung
30
Essa resposta é uma boa explicação sobre o que é poluição heap, mas na verdade não explica por que as varargs são tão propensas a causá-la que justificam um aviso específico.
Dolda2000
4
Eu também, eu estou sentindo falta de informações como garantir que meu código não contém este problema (por exemplo, como é que eu sei que tem endurecido o suficiente para acrescentar @SafeVarargs.)
Daniel Alder
237

Quando você declara

public static <T> void foo(List<T>... bar) o compilador o converte em

public static <T> void foo(List<T>[] bar) então, para

public static void foo(List[] bar)

Em seguida, surge o perigo de atribuir valores incorretos à lista por engano e o compilador não acionará nenhum erro. Por exemplo, se Tfor um String, o código a seguir será compilado sem erros, mas falhará no tempo de execução:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

Se você revisou o método para garantir que ele não contenha tais vulnerabilidades, anote-o @SafeVarargspara suprimir o aviso. Para interfaces, use @SuppressWarnings("unchecked").

Se você receber esta mensagem de erro:

O método Varargs pode causar poluição da pilha a partir do parâmetro varargs não reificável

e você tem certeza de que seu uso é seguro, você deve usá-lo @SuppressWarnings("varargs"). Consulte @SafeVarargs uma anotação apropriada para este método? e https://stackoverflow.com/a/14252221/14731 para obter uma boa explicação desse segundo tipo de erro.

Referências:

Gili
fonte
2
Eu acho que estou entendendo melhor. O perigo surge quando você lança varargs para Object[]. Contanto que você não use Object[], parece que você deve ficar bem.
Djeikyb
3
Como um exemplo de uma coisa estúpida que você poderia fazer: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. E depois ligue bar(Arrays.asList(1,2));.
djeikyb
1
@djeikyb se o perigo só surgir se eu transmitir, Object[]por que o compilador acionaria um aviso se não o fizesse? Afinal, deve ser bastante fácil verificar isso em tempo de compilação (no caso de eu não passá-lo para outra função com uma assinatura semelhante, caso em que a outra função deve acionar um aviso). Não acredito que esse seja realmente o cerne do aviso ("Você está seguro se não transmitir") e ainda não entendo;
Qw3ry #
5
@djeikyb Você pode fazer exatamente a mesma coisa estúpida sem variáveis ​​parametrizadas (por exemplo bar(Integer...args)). Então, qual é o objetivo desse aviso?
Vasiliy Vlasov
3
@VasiliyVlasov Este problema é relevante apenas para variáveis ​​parametrizadas. Se você tentar fazer o mesmo com matrizes não digitadas, o tempo de execução impedirá que você insira o tipo errado na matriz. O compilador está avisando que o tempo de execução não será capaz de impedir o comportamento incorreto porque o tipo de parâmetro é desconhecido no tempo de execução (em contraste, matrizes não sabe o tipo de seus elementos não-genéricos em tempo de execução).
Gili
8

@SafeVarargs não impede que isso aconteça, no entanto, exige que o compilador seja mais rígido ao compilar o código que o utiliza.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html explica isso em mais detalhes.

Poluição de heap é quando você obtém um ClassCastExceptionao executar uma operação em uma interface genérica e ela contém outro tipo que não o declarado.

Jontro
fonte
As restrições adicionais do compilador sobre seu uso não parecem especialmente relevantes.
Paul Bellora
6

Quando você usa varargs, isso pode resultar na criação de um Object[]para conter os argumentos.

Devido à análise de escape, o JIT pode otimizar a criação desta matriz. (Uma das poucas vezes em que o encontrei) Não é garantido que ele seja otimizado, mas não me preocuparia com isso, a menos que você veja que isso é um problema no seu perfil de memória.

O AFAIK @SafeVarargssuprime um aviso do compilador e não altera o comportamento do JIT.

Peter Lawrey
fonte
6
Interessante, embora isso realmente não responda à sua pergunta @SafeVarargs.
Paul Bellora
1
Não. Não é isso que é a poluição das pilhas "A poluição da pilha ocorre quando uma variável de um tipo parametrizado se refere a um objeto que não é desse tipo parametrizado". Re
Doradus
1

O motivo é que os varargs oferecem a opção de serem chamados com uma matriz de objetos não parametrizada. Portanto, se o seu tipo era Lista <A> ..., ele também pode ser chamado com o tipo List [] não-varargs.

Aqui está um exemplo:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

Como você pode ver, a Lista [] b pode conter qualquer tipo de consumidor e, mesmo assim, esse código é compilado. Se você usa varargs, está bem, mas se você usar a definição de método após o apagamento do tipo - teste nulo (Lista []) -, o compilador não verificará os tipos de parâmetro do modelo. @SafeVarargs suprimirá este aviso.

user1122069
fonte