6.1 El lenguaje de programación Java
En muchos lugares se dice que Java es un lenguaje de
programación seguro, pero, ¿qué quiere decir esto en
realidad?. Para contestar esta pregunta, es necesario entender
las maneras en que un lenguaje de programación puede causar
vulnerabilidades en las aplicaciones, las comunicaciones o en la
integridad de datos. Prácticamente todos estos problemas están
relacionados con el acceso a memoria. Para solucionar estos
problemas el lenguaje Java se diseño eliminando algunas
características presentes en otros lenguajes de programación
como C++ (que es del que se partió para el diseño inicial) y
añadiendo otras.
Algunos de los métodos incorporados al lenguaje Java para
corregir los problemas relacionados con los accesos a memoria
ilegales son:
- Eliminación de la aritmética con punteros. Java
la elimina por completo la aritmética con punteros, que es
una de las mayores fuentes de accesos ilegales a memoria en
otros lenguajes. De cualquier forma, esto no quiere decir
que Java no disponga de la noción de puntero, la incluye
dándole el nombre de referencia. Usando referencias
podemos definir estructuras dinámicas complejas como listas,
colas, árboles, etc. igual que en otros lenguajes.
- Comprobación de rangos en el acceso a
vectores. En Java se controlan todos los accesos a
vector y se lanza una excepción cuando intentamos un acceso
fuera de rango. En los lenguajes que esto no se hace, el
programador puede acceder a memoria que está más allá de la
reservada para el vector y modificar valores de otras
variables de forma no controlada. De hecho, el intentar
provocar estas salidas de rango en los buffers
empleados en las aplicaciones es uno de los sistemas
empleados para romper la seguridad de los programas.
- Definición del comportamiento de las variables sin
inicializar. En otros lenguajes los valores de las
variables no inicializadas están indeterminados, por lo que
si accedemos a ellas antes de darles un valor podemos
obtener resultados impredecibles. Esto en Java no es así,
toda la memoria del heap se inicializa
automáticamente, y la memoria de la pila, que es la
que emplean las variables locales, debe ser inicializada por
el programador (si en un programa se intenta usar una
variable local antes de asignarle un valor el compilador
genera un error).
- Eliminación de la liberación de memoria controlada
directamente por el programador. En otros lenguajes,
cuando un objeto creado dinámicamente deja de ser necesaro
el responsable de liberar la memoria que tiene asignada es
el programador. Es evidente que, de algún modo, es
obligarorio el liberar la memoria, ya que si no lo hacemos,
nuestra aplicación consumiría mucha más de la necesaria en
un momento dado. El problema de esta liberación es que nos
podemos equivocar y liberar objetos que aun necesitamos (con
lo que, al acceder a ellos, ya no están y leemos datos de
otros objetos o simplemente basura) e incluso intentar
liberar más de una vez la misma memoria (según el lenguaje,
compilador y sistema esto puede liberar memoria empleada por
otros objetos, causar la muerte del programa, etc.). Java
elimina este problema no proporcionando funciones de
liberación de memoria; cuando las variables u objetos ya no
son referenciadas la memoria que ocupan es liberada por un
sistema de recolección de basura automático (garbage
collection), que es parte del sistema de
ejecución.
Además de lo anterior tambien se deben mencionar otras
características del lenguaje que contribuyen a escribir código
seguro como:
- El sistema de verificación de tipos en tiempo de
compilación, que garantiza que las variables son de los
tipos correctos.
- Los niveles de acceso a los miembros de las clases, que
permite controlar la visiblidad de atributos y
métodos.
- El modificador
final
, que permite impedir que
se definan subclases cuando se aplica a una clase o que se
puedan redefinir atributos cuando se les aplica a
ellos.