Posible contaminación del montón a través del parámetro varargs


433

Entiendo que esto ocurre con Java 7 cuando se usan varargs con un tipo genérico;

Pero mi pregunta es ...

¿Qué significa exactamente Eclipse cuando dice "su uso podría contaminar el montón?"

Y

¿Cómo la nueva @SafeVarargsanotación previene esto?




Estoy viendo esto en mi editor:Possible heap pollution from parameterized vararg type
Alexander Mills

Respuestas:


252

La contaminación del montón es un término técnico. Se refiere a referencias que tienen un tipo que no es un supertipo del objeto al que apuntan.

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

Esto puede conducir a "inexplicables" ClassCastExceptions.

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

@SafeVarargsno evita esto en absoluto. Sin embargo, hay métodos que probablemente no contaminen el montón, el compilador simplemente no puede probarlo. Anteriormente, las personas que llamaban a tales API recibían advertencias molestas que eran completamente inútiles pero que tenían que ser suprimidas en cada sitio de llamadas. Ahora el autor de la API puede suprimirlo una vez en el sitio de declaración.

Sin embargo, si el método no es seguro, los usuarios ya no serán advertidos.


2
Entonces, ¿estamos diciendo que el montón está contaminado porque contiene referencias cuyos tipos no son los que podríamos esperar? (Lista <A> vs Lista <B> en su ejemplo)
hertzsprung


30
Esta respuesta es una buena explicación de lo que es la contaminación del montón, pero en realidad no explica por qué los varargs son tan particularmente propensos a causar una advertencia específica.
Dolda2000

44
Yo también, me falta información sobre cómo asegurarme de que mi código no contenga este problema (por ejemplo, ¿cómo sé que está lo suficientemente endurecido como para agregar @SafeVarargs?)
Daniel Alder

237

Cuando declaras

public static <T> void foo(List<T>... bar) el compilador lo convierte a

public static <T> void foo(List<T>[] bar) luego a

public static void foo(List[] bar)

Entonces surge el peligro de que asigne por error valores incorrectos a la lista y que el compilador no active ningún error. Por ejemplo, si Tes un String, el siguiente código se compilará sin error pero fallará en tiempo de ejecución:

// 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);

Si revisó el método para asegurarse de que no contenga tales vulnerabilidades, puede anotarlo @SafeVarargspara suprimir la advertencia. Para interfaces, use @SuppressWarnings("unchecked").

Si recibe este mensaje de error:

El método de Varargs podría causar contaminación del montón del parámetro de varargs no reificable

y está seguro de que su uso es seguro, entonces debería usarlo @SuppressWarnings("varargs"). Consulte ¿Es @SafeVarargs una anotación adecuada para este método? y https://stackoverflow.com/a/14252221/14731 para obtener una buena explicación de este segundo tipo de error.

Referencias


2
Creo que estoy entendiendo mejor. El peligro viene cuando lanzas varargs a Object[]. Mientras no lances Object[], parece que deberías estar bien.
djeikyb

3
Como un ejemplo de una cosa estúpida que podría hacer: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. Y luego llama bar(Arrays.asList(1,2));.
djeikyb

1
@djeikyb si el peligro solo se presenta si lanzo a Object[]¿ por qué el compilador activaría una advertencia si no lo hago? Después de todo, debería ser bastante fácil verificar esto en tiempo de compilación (en caso de que no lo pase a otra función con una firma similar, en cuyo caso la otra función debería activar una advertencia). No creo que este sea realmente el núcleo de la advertencia ("Estás a salvo si no lanzas"), y todavía no entiendo en qué caso estoy bien.
Qw3ry

55
@djeikyb Puede hacer exactamente lo mismo estúpido sin varargs parametrizados (por ejemplo bar(Integer...args)). Entonces, ¿cuál es el punto de esta advertencia?
Vasiliy Vlasov

3
@VasiliyVlasov Este problema solo es relevante para varargs parametrizados. Si intenta hacer lo mismo con matrices no tipadas, el tiempo de ejecución le impedirá insertar el tipo incorrecto en la matriz. El compilador le advierte de que el tiempo de ejecución será incapaz de evitar un comportamiento incorrecto porque el tipo de parámetro es desconocido en tiempo de ejecución (por el contrario, las matrices no conocen el tipo de sus elementos no genéricos en tiempo de ejecución).
Gili

8

@SafeVarargs no evita que suceda, sin embargo, exige que el compilador sea más estricto al compilar el código que lo usa.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html explica esto con más detalle.

La contaminación del montón es cuando obtienes un ClassCastExceptional hacer una operación en una interfaz genérica y contiene otro tipo que el declarado.


Las restricciones adicionales del compilador sobre su uso no parecen especialmente relevantes.
Paul Bellora

6

Cuando usa varargs, puede dar lugar a la creación de una Object[]para contener los argumentos.

Debido al análisis de escape, el JIT puede optimizar esta creación de matriz. (Una de las pocas veces que lo he encontrado lo hace) No se garantiza que esté optimizado, pero no me preocuparía a menos que vea que es un problema en su generador de perfiles de memoria.

AFAIK @SafeVarargssuprime una advertencia del compilador y no cambia el comportamiento del JIT.


66
Interesante, aunque en realidad no responde a su pregunta sobre @SafeVarargs.
Paul Bellora

1
No Eso no es lo que es la contaminación del montón. "La contaminación del montón ocurre cuando una variable de un tipo parametrizado se refiere a un objeto que no es de ese tipo parametrizado". Ref: docs.oracle.com/javase/tutorial/java/generics/…
Doradus

1

La razón es porque los varargs dan la opción de ser llamados con una matriz de objetos no parametrizados. Entonces, si su tipo era List <A> ..., también se puede llamar con el tipo List [] no varargs.

Aquí hay un ejemplo:

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

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

Como puede ver, la Lista [] b puede contener cualquier tipo de consumidor y, sin embargo, este código se compila. Si usa varargs, entonces está bien, pero si usa la definición del método después del borrado de tipo - prueba nula (Lista []) - entonces el compilador no verificará los tipos de parámetros de la plantilla. @SafeVarargs suprimirá esta advertencia.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.