No existe una protección real contra el código intencionalmente malicioso que se ejecuta dentro de la misma JVM en las API normales, como la API de recopilación.
Como se puede demostrar fácilmente:
public static void main(String[] args) throws InterruptedException {
Object[] array = { "foo", "bar", "baz", "and", "another", "string" };
array[array.length - 1] = new Object() {
@Override
public String toString() {
Collections.shuffle(Arrays.asList(array));
return "string";
}
};
doThing(new ArrayList<String>() {
@Override public Object[] toArray() {
return array;
}
});
}
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
System.out.println("made a safe copy " + newStrs);
for(int i = 0; i < 10; i++) {
System.out.println(newStrs);
}
}
made a safe copy [foo, bar, baz, and, another, string]
[bar, and, string, string, another, foo]
[and, baz, bar, string, string, string]
[another, baz, and, foo, bar, string]
[another, bar, and, foo, string, and]
[another, baz, string, another, and, foo]
[string, and, another, foo, string, foo]
[baz, string, foo, and, baz, string]
[bar, another, string, and, another, baz]
[bar, string, foo, string, baz, and]
[bar, string, bar, another, and, foo]
Como puede ver, esperar un List<String>
no garantiza realmente obtener una lista de String
instancias. Debido a la eliminación de tipos y tipos sin formato, ni siquiera hay una solución posible en el lado de implementación de la lista.
La otra cosa de la que puede culpar ArrayList
al constructor es la confianza en la toArray
implementación de la colección entrante . TreeMap
no se ve afectado de la misma manera, sino solo porque no hay tal ganancia de rendimiento al pasar la matriz, como en la construcción de un ArrayList
. Ninguna clase garantiza una protección en el constructor.
Normalmente, no tiene sentido intentar escribir código suponiendo código intencionalmente malicioso en cada esquina. Hay demasiado que puede hacer para protegerse contra todo. Dicha protección solo es útil para el código que realmente encapsula una acción que podría otorgarle a una persona que llama maliciosa acceso a algo, ya que no podría acceder sin este código.
Si necesita seguridad para un código en particular, use
public static void doThing(List<String> strs) {
String[] content = strs.toArray(new String[0]);
List<String> newStrs = new ArrayList<>(Arrays.asList(content));
System.out.println("made a safe copy " + newStrs);
for(int i = 0; i < 10; i++) {
System.out.println(newStrs);
}
}
Luego, puede estar seguro de que newStrs
solo contiene cadenas y no puede ser modificado por otro código después de su construcción.
O úselo List<String> newStrs = List.of(strs.toArray(new String[0]));
con Java 9 o más reciente
Tenga en cuenta que Java 10's List.copyOf(strs)
hace lo mismo, pero su documentación no indica que está garantizado que no confíe en el toArray
método de la colección entrante . Entonces List.of(…)
, llamar , que definitivamente hará una copia en caso de que devuelva una lista basada en una matriz, es más seguro.
Como ninguna persona que llama puede alterar la forma, las matrices funcionan, volcar la colección entrante en una matriz, seguido de llenar la nueva colección con ella, siempre hará que la copia sea segura. Dado que la colección puede contener una referencia a la matriz devuelta como se demostró anteriormente, podría alterarla durante la fase de copia, pero no puede afectar la copia en la colección.
Por lo tanto, cualquier verificación de coherencia debe realizarse después de que el elemento particular se haya recuperado de la matriz o de la colección resultante en su conjunto.
NavigableSet
y otrasComparable
colecciones basadas a veces pueden detectar si una clase no se implementacompareTo()
correctamente y lanzar una excepción. No está claro qué quiere decir con argumentos no confiables. ¿Te refieres a un malhechor que crea una colección de cadenas malas y cuando las copias a tu colección, algo malo sucede? No, el marco de colecciones es bastante sólido, existe desde 1.2.HashSet
(y todas las demás colecciones de hash en general) se basa en la corrección / integridad de lahashCode
implementación de los elementos,TreeSet
yPriorityQueue
depende deComparator
(y ni siquiera puede crea una copia equivalente sin aceptar el comparador personalizado si lo hay),EnumSet
confía en la integridad delenum
tipo particular que nunca se verifica después de la compilación, por lo que un archivo de clase, no generadojavac
o hecho a mano, puede subvertirlo.new TreeSet<>(strs)
dóndestrs
está aNavigableSet
. Esta no es una copia masiva, ya que el resultadoTreeSet
utilizará el comparador de la fuente, que incluso es necesario para mantener la semántica. Si está bien con solo procesar los elementos contenidos, estetoArray()
es el camino a seguir; incluso mantendrá el orden de iteración. Cuando está bien con "tomar elemento, validar elemento, usar elemento", ni siquiera necesita hacer una copia. Los problemas comienzan cuando desea verificar todos los elementos, seguido de la utilización de todos los elementos. Entonces, no puede confiar en unaTreeSet
copia con un comparador personalizadocheckcast
para cada elemento, estoArray
con un tipo específico. Siempre terminamos en eso. Las colecciones genéricas ni siquiera conocen su tipo de elemento real, por lo que sus constructores de copias no pueden proporcionar una funcionalidad similar. Por supuesto, puede diferir cualquier verificación al uso previo correcto, pero entonces, no sé a qué apuntan sus preguntas. No necesita "integridad semántica", cuando está de acuerdo con verificar y fallar inmediatamente antes de usar elementos.