Liliana Badillo Sánchez

Anuncio
cenidet
Centro Nacional de Investigación y Desarrollo Tecnológico
Departamento de Ciencias Computacionales
TESIS DE MAESTRÍA EN CIENCIAS
Análisis del Problema para Determinar un
Procedimiento que Deduzca a Partir de Código Legado
Pre y Pos Condiciones e Invariantes
Presentada por
Liliana Badillo Sánchez
L.M.A. por la Universidad Juárez del Estado de Durango
Como requisito para la obtención del grado de:
Maestro en Ciencias en Ciencias de la Computación
Director de tesis:
Dr. René Santaolaya Salgado
Co-Director de tesis:
M.C. Olivia Graciela Fragoso Díaz
Cuernavaca, Morelos, México.
Noviembre de 2008
i
ÍNDICE
ÍNDICE DE FIGURAS ............................................................................................................. iv
ÍNDICE DE TABLAS ............................................................................................................... v
SIMBOLOGÍA ......................................................................................................................... ivi
GLOSARIO .............................................................................................................................. vii
Capítulo INTRODUCCIÓN ....................................................................................................... 1
1.1. Introducción. ....................................................................................................................... 2
1.2. Objetivo. .............................................................................................................................. 2
1.3. Descripción del problema.................................................................................................... 2
1.4. Justificación. ........................................................................................................................ 2
1.5. Beneficios. ........................................................................................................................... 2
1.6. Alcances. ............................................................................................................................. 2
1.7. Limitaciones. ....................................................................................................................... 3
1.8. Organización de esta tesis. .................................................................................................. 3
Capítulo 2 MARCO TEÓRICO ............................................................................................... 21
2.1. Aserción ............................................................................................................................ 21
2.2. Código anotado. .................................................................................................................. 4
2.3. Lógica de Hoare. ................................................................................................................. 5
2.4. Axiomas y reglas de la lógica de Hoare. ............................................................................. 6
2.4.1. Axioma de asignación. ............................................................................................. 7
2.4.2. Regla de composición .............................................................................................. 7
2.4.3. Regla del comando if ................................................................................................ 8
2.4.4. Reforzamiento de la precondición............................................................................ 8
2.4.5. Debilitamiento de la poscondición. .......................................................................... 8
2.4.6. Regla del comando while. ........................................................................................ 9
2.4.7. Otras reglas de la lógica de Hoare. .......................................................................... 9
2.5. Cálculo de la precondición más débil wp ............................................................................ 9
2.5.1. Ejemplo del cálculo de la precondición más débil wp ....................................... 10
2.6. Extensión de la lógica de Hoare para programación orientada a objetos ......................... 12
2.6.1. Diseño por contratos........................................................................................... 12
2.6.2. Invariante de clase .............................................................................................. 16
2.7. Correctitud en programación orientada a objetos ............................................................. 16
2.8. Mecanización de la verificación de programas ................................................................. 17
2.9. Verificación de Modelos ................................................................................................... 18
2.9.1. Verificación de Modelos Temporal................................................................... 18
2.9.2. Lógica Temporal Lineal (LTL) ........................................................................... 19
2.9.3. Sintaxis de LTL................................................................................................... 19
2.9.4. Semática de LTL. ................................................................................................ 19
Capítulo 3 ANTECEDENTES ................................................................................................. 21
3.1. Antecedentes. .................................................................................................................... 21
3.1.1. Identificación de funciones recurrentes en software legado ................................. 21
3.2. Estado del arte. .................................................................................................................. 22
3.3. Trabajos relacionados. ....................................................................................................... 23
3.3.1. Análisis de código fuente ............................................................................................ 23
3.3.1.1. Extended Static Checking for Java ESC/Java ..................................................... 23
3.3.1.2. Houdini, An annotation assistant for ESC/Java . ................................................ 23
3.3.2. Análisis de documentación.......................................................................................... 23
3.3.2.1. Uncovering hidden contracts: The .NET example ............................................. 24
3.3.3. Análisis dinámico ........................................................................................................ 24
ii
3.3.3.1. The Daikon system for dynamic detection of likely invariants .......................... 24
3.3.4. Análisis de documentación y análisis dinámico .......................................................... 24
3.3.4.1. Extracting functional and Non functional contracts from Java Classes and
Enterprise Java Beans ...................................................................................................... 25
3.3.4.2. Discern: Towards the Automatic Discovery of Software Contracts .................. 25
Capítulo 4 ANÁLISIS DEL PROBLEMA Y SOLUCIÓN PROPUESTA ............................. 26
4.1. Técnicas y herramientas que pueden ayudar en la extracción .......................................... 27
4.1.1. Análisis de documentación..................................................................................... 27
4.1.2. Análisis del código fuente ...................................................................................... 27
4.1.3. Lógica de Hoare y cálculo de la precondición más débil wp................................. 27
4.1.4. Herramientas disponibles en Internet ..................................................................... 28
4.1.4.1. Análisis dinámico ........................................................................................... 28
4.1.4.2. Generación automática de invariantes............................................................ 28
4.2. Etapas del problema de la extracción ................................................................................ 29
4.3. Solución propuesta ............................................................................................................ 30
4.4. Formalización de la solución propuesta ............................................................................ 32
Capítulo 5 PRUEBAS .............................................................................................................. 34
5.1. Caso de prueba 1 ............................................................................................................... 34
5.2. Caso de prueba 2 ............................................................................................................... 38
5.3. Caso de prueba 3 ............................................................................................................... 40
5.4. Caso de prueba 4 ............................................................................................................... 42
Capítulo 6 CONCLUSIONES .................................................................................................. 44
6.1. Conclusiones. .................................................................................................................... 44
6.2. Aportaciones...................................................................................................................... 44
6.2. Trabajo futuro. ................................................................................................................... 45
REFERENCIAS ....................................................................................................................... 46
ANEXO A ................................................................................................................................ 48
iii
ÍNDICE DE FIGURAS
Figura 1. Especificación de un contrato de software.. ............................................................. 12
Figura 2. Ejemplo de la declaración de una rutina en el lenguaje de programación Eiffel ......13
Figura 3. Atributos de un contracto en CDL. ........................................................................... 15
Figura 4. Estructura de especificación de un contrato.. ........................................................... 16
Figura 5. Ejemplo de salida de Daikon.. .................................................................................. 28
Figura 6. Tareas de la extracción de especificaciones formales.. ............................................ 29
Figura 7. Algoritmo propuesto para la extracción de especificaciones formales..................... 31
Figura 8. Código del never claim generado en Promela. ......................................................... 32
Figura 9. Salida de Spin para el algoritmo propuesto .............................................................. 33
iv
ÍNDICE DE TABLAS
Tabla 1. Capacidad de automatización de técnicas y herramientas. ........................................ 30
Tabla 2. Especificación formal de la clase Rectangle. ............................................................. 37
Tabla 3. Especificación formal de la clase Square................................................................... 37
Tabla 4. Especificación formal de la función factorial. ........................................................... 40
Tabla 5. Especificación formal de la función multiplic. .......................................................... 42
Tabla 6. Especificación formal de la clase Stack. .................................................................... 43
v
SIMBOLOGÍA
Vc
⊢
≡
i.e.
∧
∨
∃
◊
□
U
W
⊤
⊥
∀
Condición de verificación
Inferencia
Equivalencia
Es decir
Y, “and”
O, “or”
Existe al menos un elemento
Siguiente
Eventualmente
Siempre
Fuerte hasta
Débil hasta
Tautología
Contradicción
Para todo
vi
GLOSARIO
ASERCIÓN
Una aserción es una terna {P}S{Q}, donde S es un programa
y P ,Q son formulas de la lógica de predicados (conocidas
como precondición y poscondición respectivamente). Una
aserción es verdadera si y sólo si, S inicia en el estado que
satisface P y si la computación de S termina, entonces la
computación termina en el estado que satisface Q.
CÓDIGO ANOTADO
Un código o comando anotado tiene aserciones embebidas
(precondición, poscondición e invariantes).
CÓDIGO LEGADO
Código fuente heredado de alguien más.
CENIDET
Centro Nacional de Investigación y Desarrollo Tecnológico.
CONDICIONES DE
VERIFICACIÓN
De las especificaciones anotadas en el programa se genera
un conjunto de sentencias puramente matemáticas llamadas
verification conditions (condiciones de verificación o vc’s).
Las vc’s se verifican automáticamente en un demostrador de
teoremas.
CONTRATO
Involucra generalmente dos partes, cada una de ellas espera
algún beneficio y está preparada a cumplir algunas
obligaciones en intercambio. Los beneficios y las
obligaciones son plasmados en un documento. En Ingeniería
de Software se aplica éste concepto como: “si la ejecución
de una cierta tarea recae en una llamada de rutina que
maneja sus sub-tareas, es necesario especificar la relación
entre el cliente (el que llama a la rutina) y el proveedor (la
rutina llamada) lo más preciso posible”.
CORRECTITUD
Un algoritmo es correcto si cumple con lo siguiente:
resuelve el problema computacional para el cual fue
diseñado; para cada entrada produce la salida deseada; y su
ejecución termina en un tiempo finito.
CORRECTITUD
PARCIAL
Si se cumple que ⊢ {P}S{Q} entonces la aserción es
parcialmente correcta con respecto a P y Q.
DEMOSTRACIÓN
AUTOMÁTICA DE
TEOREMAS
Demostración de teoremas matemáticos mediante una
computadora.
DISEÑO POR
CONTRATOS
Es una metodología para el diseño e implementación de
aplicaciones y componentes. En ésta se definen
precondiciones, poscondiciones e invariantes para cada
método de una clase.
vii
ESPECIFICACIÓN
FORMAL
Descripción matemática de software o hardware que se
utiliza para desarrollar una implementación.
INVARIANTE DE
CICLO
Es una condición que se cumple al terminar cada iteración
de un ciclo.
INVARIANTE DE
CLASE
Expresa propiedades globales inmutables de las instancias de
una clase.
LÓGICA DE HOARE
Permite probar la verdad o falsedad de propiedades de
programas imperativos (especialmente correctitud y
terminación).
CÁLCULO WP(S,Q)
También conocido como cálculo de la precondición más
débil wp. Es un transformador de predicados que define la
transformación de un predicado de poscondición en un
predicado de precondición para cualquier fragmento de un
programa.
MODEL CHECKING
Técnica que se utiliza para demostrar que se mantengan
algunas propiedades a través del tiempo de un modelo
formalmente especificado.
PRECONDICIÓN
Sentencia lógica que hace referencia al estado inicial de la
parte de un código.
POSCONDICIÓN
Sentencia lógica que hace referencia al estado final de la
parte de un código
viii
CAPÍTULO 1
INTRODUCCIÓN
Capítulo 1) INTRODUCCIÓN
En este capítulo se da una breve introducción al tema de tesis, el objetivo, alcances y
limitaciones, entre otros. Además, se lista el contenido de los capítulos siguientes.
1.1. Introducción
Una de las actuales corrientes de investigación está orientada hacia la búsqueda, clasificación
y recuperación de servicios y componentes de reuso utilizando la especificación de su
funcionalidad. Para esto, es necesario que cada componente de reuso y servicio tenga definida
explícitamente su funcionalidad.
Los elementos que constituyen la especificación formal de la funcionalidad son:
precondición, poscondición e invariantes.
La especificación formal de la funcionalidad de código fuente no es común en la
práctica debido a la complejidad que representa para el desarrollador. Documentar
correctamente código fuente ayuda a prevenir muchos errores. Desafortunadamente escribir y
mantener dicha documentación requiere una inversión considerable de tiempo y esfuerzo por
parte de los programadores. Sin embargo, esta inversión no es un desperdicio, por el
contrario, ayuda a encontrar defectos que tienen un costo elevado (dinero y esfuerzo).
Sería de gran beneficio una herramienta que analizara código fuente existente y
extrajera de manera automática sus especificaciones (precondiciones, poscondiciones e
invariantes). Sin embargo, se considera imposible el desarrollo de dicha herramienta debido a
razones teóricas y prácticas.
En esta tesis se analizó el problema de extraer especificaciones a partir de código
fuente. Se analizaron las técnicas y herramientas existentes que ayudan en el proceso y se
identificaron las etapas que deben seguirse para deducir precondiciones, poscondiciones e
1
CAPÍTULO 1
INTRODUCCIÓN
invariantes. A partir de este análisis se propuso un algoritmo de decisión y se formalizó
utilizando una herramienta de verificación de modelos.
1.2. Objetivo
El objetivo de esta tesis es determinar la forma de extraer la especificación formal de
componentes de reuso o servicios a partir de código fuente. Para evitar que el desarrollador,
invierta una considerable cantidad de tiempo y esfuerzo, en la especificación manual de
precondiciones, poscondiciones e invariantes de clase.
1.3. Descripción del problema
La especificación formal de la funcionalidad de código fuente no es común en la práctica.
Requiere una considerable inversión de tiempo y esfuerzo por parte del programador de la
aplicación. El tiempo y esfuerzo invertidos en la especificación formal de la funcionalidad no
representa una pérdida, por el contrario ayuda en la prevención y detección de errores en el
desarrollo de software.
Una herramienta que analizara y extrajera especificaciones formales a partir de código
fuente, sería de gran beneficio en la reducción de tiempo y esfuerzo requeridos para la
realización de esta tarea.
El problema radica en que no se conoce la forma de determinar las precondiciones,
poscondiciones e invariantes de clase de manera semiautomática o automática a partir de
código fuente. No se tiene conocimiento de la existencia de un procedimiento que sea
completamente automatizable, para que pueda ser implementado en una herramienta que
determine automáticamente especificaciones formales de código fuente.
1.4. Justificación
Varios investigadores han observado en sus reportes que conocer formalmente las
precondiciones, poscondiciones e invariantes de operaciones relacionadas con diferentes
dominios ayudaría a resolver muchos de los problemas de recuperación y empate de
información, prueba de correctitud y eliminación de errores. Así mismo, la especificación
formal de la funcionalidad del código fuente es de gran ayuda en la prevención y detección de
errores en etapas tempranas del desarrollo de software. Además, asegura de manera formal la
correctitud del producto final. Sin embargo no se sabe si es factible ni se conoce la
complejidad a la que se enfrentaría un sistema que automatice la extracción de
especificaciones formales a partir de código fuente.
1.5. Beneficios
Al analizar la viabilidad y los diferentes escenarios de éxito y fracaso, en un proceso de
extracción de la especificación formal de código fuente se podría plantear una técnica o
procedimiento para la automatización de esta tarea. Dicha técnica o procedimiento tendría
como posible aplicación la localización automática tanto de servicios como de componentes
de reuso. Además de ayudar en la detección de errores en etapas tempranas del desarrollo y el
aseguramiento de la calidad del software.
1.6. Alcances
El alcance que presenta esta tesis, es un reporte de alternativas viables para la obtención de
especificaciones formales a partir de código fuente. Y la propuesta de un procedimiento, que
involucra técnicas encontradas en la literatura especializada.
2
CAPÍTULO 1
INTRODUCCIÓN
1.7. Limitaciones
En esta sección se mencionan las limitaciones que presenta el procedimiento propuesto:
•
•
•
Al ser un trabajo de análisis no hay desarrollo de una herramienta.
El procedimiento planteado requiere de conocimiento especializado para su ejecución.
Al no existir desarrollo de una herramienta, se realizaron pruebas de forma manual.
1.8. Organización de esta tesis.
A continuación se describe un panorama de la organización de este documento de tesis.
Capítulo 2.
Se presentan los conceptos y temas relacionados con la presente investigación,
necesarios para dar el fundamento teórico para que el lector o las personas interesadas
en esta investigación se familiaricen con el tema.
Capítulo 3.
Se presentan los antecedentes de esta tesis. El antecedente es un trabajo de
investigación llevado a cabo en el CENIDET que está relacionado con esta
investigación. Se documenta el estado del arte y los trabajos relacionados a esta tesis.
Capítulo 4.
Se presenta un estudio detallado de las técnicas y herramientas que fueron analizadas y
que son la base para el desarrollo del algoritmo de decisión que se propone en este
trabajo. Además se describe el procedimiento para la formalización del algoritmo de
decisión propuesto.
Capítulo 5.
Se presentan cuatro casos de prueba que se realizaron para ilustrar el funcionamiento
del algoritmo propuesto.
Capítulo 6.
Se describen las conclusiones de la tesis, así como también las aportaciones y los
trabajos futuros.
3
CAPÍTULO 2
MARCO TEÓRICO
Capítulo 2) MARCO TÉORICO
En este capítulo se describen: la lógica de Hoare; el método de la precondición más débil
(wp); la generación de condiciones de verificación; verificación de modelos para validación;
entre otros. Estos elementos se encuentran fuertemente ligados con el desarrollo de este
trabajo y en conjunto forman el marco teórico que respalda esta tesis.
2.1. Aserción
Una aserción es una terna {P}S{Q}, donde S es un programa y P ,Q son formulas de la lógica
de predicados (conocidas como precondición y poscondición respectivamente). Una aserción
es verdadera si y sólo si, S inicia en el estado que satisface P y si la computación de S termina,
entonces la computación termina en el estado que satisface Q. Lo anterior se denota
⊢{P}S{Q}. Si se cumple que ⊢{P}S{Q} entonces la aserción es parcialmente correcta (i.e.
correctitud parcial) con respecto a P y Q. A esta notación también se le conoce como terna
de Hoare.
2.2. Código anotado
Un código o comando anotado tiene aserciones embebidas (precondición, poscondición e
invariantes). Un comando esta anotado correctamente si las aserciones han sido insertadas en
los siguientes lugares:
Antes de cada comando Ci (donde i>1) en la secuencia C1;C2;…;Cn.
Después de la palabra do en el comando while y en el for en el caso de las invariantes
de ciclo.
De aquí en adelante las anotaciones en código fuente se representan con { }.
4
CAPÍTULO 2
MARCO TEÓRICO
Ejemplo: En el siguiente código las aserciones deben insertarse en los puntos 1 y 2 para
estar propiamente anotado. La precondición y la poscondición (indicadas entre { }) fueron
anotadas previamente por el programador.
Donde 1 la precondición del ciclo while se expresa como {Y=1 ∧ X=n} y 2 la invariante de
ciclo como {Y * X! = n!}.
2.3. Lógica de Hoare
Fue desarrollada por C.A.R. Hoare [4] y permite probar la verdad o falsedad de propiedades
de programas imperativos (especialmente corrección y terminación). Está basada en la idea de
diagrama de flujo anotado.
Para demostrar las propiedades mencionadas anteriormente se utilizan axiomas y
reglas de inferencia como en cualquier sistema deductivo.
Se verifica cada comando c de un programa. Si el control de un programa ejecuta el
comando c con entrada a con P (precondición) verdadero, entonces el control debe dejar el
comando con salida b con Q verdadero (poscondición). Lo anterior se demuestra verdadero
mediante condiciones de verificación Vc(P;Q) con los antecedentes y consecuentes de c como
premisas. Los axiomas utilizados se mencionan a continuación:
Sean Vc(P;Q) y Vc(P0;Q0) entonces:
Axioma 1. Vc ( P ∧ P’; Q ∧ Q’)
El axioma 1 significa que si existen pruebas separadas de que un programa tiene ciertas
propiedades, es posible formar una conjunción de ambas. Ya que es bien sabido que en lógica
de predicados si se tienen dos premisas válidas, entonces su conjunción también lo es.
Axioma 2. Vc(P ∨ P’; Q ∨ Q’)
El axioma 2 es útil para combinar los resultados de un caso de análisis.
Axioma 3. Vc ( ( ∃x)( P ) ; ( ∃x ) ( Q ) )
El axioma 3 establece que si la variable x tiene la propiedad P antes de ejecutar el comando c,
entonces tiene la propiedad Q después de la ejecución.
Axioma 4. Si Vc ( P ; Q ) y R⊢P, Q⊢S, entonces Vc ( R ; S ).
El axioma 4 dice que si P es antecedente y Q consecuente del comando c, entonces también
son válidos cualquier antecedente más fuerte que P y cualquier consecuente más débil que Q.
Corolario 1. Si Vc ( P ; Q ) y ⊢( P ≡ R), ⊢( Q ≡ S ), entonces Vc ( R ; S ).
El primer paso para hacer un razonamiento válido acerca de un programa es conocer
las operaciones elementales que realiza (suma, multiplicación y división de enteros). La
aritmética de las computadoras no es la misma que la aritmética matemática y es necesario
poner cuidado en la selección de los axiomas apropiados.
5
CAPÍTULO 2
MARCO TEÓRICO
Una vez seleccionado alguno los axiomas presentados anteriormente, es posible
utilizarlo para la deducción de propiedades de programas. Estas propiedades se especifican
con sentencias en lógica de primer orden (aserciones). Las aserciones no atribuyen valores
particulares las variables de un programa, especifican propiedades generales sus posibles
valores y las relaciones entre ellos.
La validez de los resultados de un programa (o parte de él) depende de los valores que
tomen las variables antes de su ejecución. Ésta precondición inicial de éxito, es especificada
por el mismo tipo de aserciones usadas, para describir los resultados obtenidos al término del
programa.
Se utiliza la notación {P} Q {R} para establecer la conexión entre la precondición
(P), un programa (Q) y la descripción del resultado de la ejecución (R) [4]. La notación
anterior se interpreta como: "si la aserción P es verdadera al inicio del programa Q, entonces
la aserción R es verdadera al término de la ejecución del mismo". Si no existen
precondiciones impuestas, se escribe Q{R}. Los axiomas para el razonamiento sobre
programas se presentan a continuación.
2.4. Axiomas y reglas de la lógica de Hoare
Partiendo de una especificación (una poscondición y posiblemente alguna precondición), se
demuestra que el programa cumple con la funcionalidad requerida. Generalmente, después de
analizar unas cuantas líneas de código, la prueba se hace demasiado compleja para
representarla en forma de inferencias encadenadas. Para resolver este problema se insertan en
el código anotaciones con la siguiente representación:
Dado S ≡ S1;S2;…Sn, si queremos demostrar {P0}S{Pn} es necesario probar:
{P0}S1{P1} {P1}S2{P2} . . . {Pn-1}Sn{Pn}
Y se usa la regla de composición n-1 veces.
Se representa la prueba de {P0}S{Pn} como:
{P0} anotación
S1
{P1} anotación
S2
...
{Pn-1} anotación
Sn
{Pn} anotación
Las fórmulas P1,…,Pn-1 son condiciones intermedias y cada paso: {Pi-1} Si {Pi} será
obtenido a través de alguna de las reglas de la lógica de Hoare.
Se obtiene una proposición mediante las reglas de inferencia pertenecientes a esta
lógica P’0 al principio de la secuencia de comandos. Ésta garantiza que al ejecutar S se
verifica la poscondición Pn. Se verifica si es posible obtener P’0 a partir de la precondición P0
mediante la regla de reforzamiento de la precondición.
Se demuestra que P0 ⊢ P’0 usando cualquiera de los sistemas deductivos de la lógica
de predicados. Este tipo de demostraciones suelen omitirse en la secuencia de anotaciones e
instrucciones.
6
CAPÍTULO 2
MARCO TEÓRICO
Los axiomas en la lógica de Hoare se especifican por esquemas, los cuales son
instanciados para obtener especificaciones particulares de correctitud parcial. Las reglas de
inferencia son especificadas con la siguiente notación:
La cual significa que la conclusión ⊢S es deducida de la hipótesis ⊢S1, … ,Sn.
Las hipótesis son los teoremas de la lógica de Hoare o una mezcla entre ellos y
diversos teoremas de otras ramas de las matemáticas.
2.4.1. Axioma de asignación
Si el estado inicial es s, entonces el estado s’ después de la asignación es igual a s,
sustituyendo la variable V por el resultado de evaluar E. Si s’ satisface P (la hace cierta), la
fórmula que satisface s sería P[E/V], el resultado de sustituir todas las ocurrencias libres de V
en P por E.
Ejemplo: Sea a =b/2-1 un comando de asignación y sea { a < 10 } su poscondición. La
precondición es computada sustituyendo b /2 -1 en la aserción {a<10}, como sigue:
b/2 -1 < 10
b/2 < 10+1
b < 11 * 2
Por lo tanto b < 22
Así la precondición para la asignación dada es {b < 22}.
2.4.2. Regla de composición
Esta regla aplica la transitividad sobre dos comandos para poder obtener las
precondiciones y poscondiciones de la concatenación de ambas.
Ejemplo:
y = 3 * x +1
x = y + 3;
{x<10}
La precondición para el último comando de asignación es y<7, entonces la usamos
como poscondición para el primer estatuto.
3 * x +1<7
3*x<7-1
x<6/3
x<2
7
CAPÍTULO 2
MARCO TEÓRICO
2.4.3. Regla del comando if
Los bloques S1 y S2 tienen la misma poscondición Q. La conjunción de la fórmula P con la
condición B y respectivamente con su negación ¬B son sus precondiciones. P aparece como
precondición de la selección y Q como su poscondición.
Ejemplo:
if (x>0)
y = y – 1;
else
y = y +1;
Supongamos que Q={y>0} es la poscondición para este estatuto. Podemos usar el
axioma para la asignación en la cláusula then:
y = y – 1 {y > 0} esto produce {y-1>0} o {y>1}.
Ahora aplicamos el mismo axioma a la clausula else:
y = y +1 {y>0} esto produce la precondición {y + 1>0} o {y > -1}.
Ya que {y > 1}=>{y > -1}, la regla del if permite usar P={y>1} para la precondición del
estatuto de selección.
2.4.4. Reforzamiento de la precondición (implicación izquierda)
Esta regla asume una precondición más fuerte que la existente sin perder la corrección
de la demostración del programa anotado. Es decir, se puede tomar una precondición más
fuerte que la inicial para la prueba de propiedades de un programa. Se importan pruebas de la
lógica de predicados, concretamente R ⊢ P (lo cual significa que de R se deduce P).
2.4.5. Debilitamiento de la poscondición (implicación derecha)
Se asume una poscondición más débil que la existente de manera análoga a la regla anterior.
Es posible tomar una poscondición más débil que la inicial para la prueba de propiedades de
un programa. Las pruebas de la lógica de predicados que se importan son de la forma Q ⊢ R.
8
CAPÍTULO 2
MARCO TEÓRICO
2.4.6. Regla del comando while (correctitud parcial)
Con esta regla se calculan las precondiciones y poscondiciones de un ciclo while
satisfaciendo la propiedad de correctitud parcial. Si la conjunción de las condiciones P y B
es la precondición de S y P es su poscondición, quiere decir que cuando P y B son ciertas, P
sería cierta tras la ejecución de S. Si B es la condición del ciclo while se demuestra que siendo
cierta la precondición P, se cumple la pos condición P∧¬B, dado que P era poscondición de
la(s) instrucción(es) S del interior del ciclo y ¬B debe cumplirse para salir del while.
Para demostrar {R} while B do S {Q}, se debe encontrar una proposición P (invariante de
ciclo i.e. lo que no varía durante su ejecución) que verifique:
1. R ⊢ P para el reforzamiento de la precondición (i.e. P se deduce de R).
2. P∧¬B ⊢Q para el debilitamiento de la poscondición (i.e. de P∧¬B se deduce Q).
3. {P} while B do S { P∧¬B } (utilizando la regla del while de la lógica de Hoare).
Encontrar invariantes de ciclo requiere ingenio. Para esto, hay algunas reglas heurísticas:
1. Modificar la poscondición del while para hacerla dependiente del índice del ciclo (la
variable que crece o decrece en cada iteración).
2. Si la invariante P aún no verifica, se debe reforzar P para que se cumpla.
Existen diversos enfoques para inferir invariantes de ciclo de manera semiautomática o
automática como: el uso de ecuaciones diferenciales; análisis dinámico; análisis estático; y
ejecución simbólica.
2.4.7. Otras reglas de la lógica de Hoare
2.5. Cálculo de la precondición más débil (wp)
Éste cálculo requiere de las siguientes definiciones:
Definición 2.1.
Una fórmula A es más débil que la fórmula B si B → A. Dado un conjunto de fórmulas
{A1, A2,…}, Ai es la más débil en el conjunto si Aj → Ai para toda j.
Se puede reforzar una premisa y debilitar una consecuencia. Por ejemplo, si P → Q,
entonces (P ∧ R) → Q y P → (Q ∨ R).
9
CAPÍTULO 2
MARCO TEÓRICO
Definición 2.2.
Dado el programa S y la fórmula Q, la precondición más débil de S y Q wp(S,Q) es la
fórmula más débil P tal que ⊢ {P}S{Q}.
Lema 2.1.
⊢ {P}S{Q} si y solo si ⊢ P → wp(S,Q) .
wp es un transformador de predicados que define la transformación de un predicado de
poscondición en un predicado de precondición para cualquier fragmento de un programa. El
razonamiento que utiliza wp es conocido como “hacia atrás” (backwards). Comienza a partir
de la especificación del resultado del programa y se calcula hacia atrás la derivación de cada
comando individualmente, hasta encontrar el predicado de precondición más débil. El cálculo
de la precondición más débil es la base para la derivación de programas a partir de su
especificación (refinamiento de programas) y su demostración formal.
2.5.1 Ejemplo del cálculo de la precondición más débil.
Se tiene el siguiente programa que calcula la división de dos números:
Se observa que el programa tiene dos asignaciones y un ciclo while (éste con dos
asignaciones más). Se calculan las anotaciones de abajo hacia arriba (método de la
precondición más débil wp) con las reglas de asignación y composición. El primer paso es
calcular la invariante del ciclo, una fórmula P que verifique la regla del while parcial.
Utilizando una de las heurísticas propuestas se toma la poscondición del programa. Se
propone la invariante x=a*y+b ∧ b≤0.
Ahora se demuestra la invariante propuesta con la regla del while parcial. Se verifica
que se cumpla {P∧B}S{P} donde P es la invariante de ciclo. El primer paso es aplicar la regla
de asignación:
{x=(a+1)*y+b ∧ b≥0} a=a+1; {x=a*y+b ∧ b≥0}
Se aplica de nuevo la regla de asignación al siguiente comando:
{x=(a+1)*y+b-y ∧ b-y≥0} b=b-y;{ x=(a+1)*y+b ∧ b≥0}
Aplicando la regla de la composición:
{x = (a+1) ∗ y+b−y ∧ b−y ≥ 0} b = b−y; a = a+1; {x = a ∗ y+b ∧ b ≥ 0}
Haciendo reforzamiento de la precondición se demuestra {P ∧ B}S{P}:
P
B
10
CAPÍTULO 2
MARCO TEÓRICO
(x =a∗y+b ∧ b ≥ 0 ∧ b ≥ y) → (x=(a+1)∗y+b−y ∧ b−y ≥ 0)
Aplicando técnicas algebraicas se llega a la conclusión de que es cierta la proposición
anterior. Se factoriza el valor de a y se despeja el valor de y en la primera sentencia. Se
deduce entonces que ambas proposiciones son equivalentes. Ahora se utiliza la regla del while
parcial para demostrar:
A partir de la invariante, se asciende en el programa aplicando la regla de asignación
dos veces:
{x=a∗y+x ∧ x ≥ 0} b = x; {x=a∗y+b ∧ b ≥ 0}
{x=x ∧ x ≥ 0} a = 0; {x=a∗y+x ∧ x ≥ 0}
Se aplica composición en las anteriores:
{x=x ∧ x ≥ 0} a = 0; b = x; {x=a∗y+b ∧ b ≥ 0}
Como la poscondición del ciclo coincide con la del programa, queda por demostrar la
precondición. Con la regla de reforzamiento se obtiene la siguiente condición de verificación:
(x ≥ 0 ∧ y ≥ 0) → (x=x ∧ x ≥ 0)}
Lo anterior se demuestra de la siguiente manera:
1. x ≥ 0 ∧ y ≥ 0
2. x ≥ 0
3. x=x
4. x=x ∧ x ≥ 0
Premisa
Eliminación de ∧ de 1
Axioma de números naturales
Conjunción de 2 y 3
Se resume la demostración intercalando las anotaciones en el programa:
11
CAPÍTULO 2
MARCO TEÓRICO
2.6. Extensión de la lógica de Hoare para programación orientada a objetos
Un programa orientado a objetos es una colección de clases. Cada clase define una colección
de objetos de cierto tipo y un conjunto de funciones (métodos). Además de la lógica de Hoare
son necesarias herramientas adicionales para asegurar que programas orientados a objetos
sean correctos. La primera de ellas es la metodología de Diseño por Contratos y la segunda es
el concepto de invariante de clase.
2.6.1. Diseño por Contratos
El contrato humano involucra generalmente dos partes, cada una de ellas espera algún
beneficio y está preparada a cumplir algunas obligaciones en intercambio. Los beneficios y
las obligaciones (i.e. contrato) son plasmados en un documento. Éste protege ambas partes: al
cliente especificando cuánto puede obtener y al proveedor diciéndole qué tan poco es
aceptable. Se observa que lo que es una obligación para una parte, es un beneficio para la otra.
Esta noción de contrato se aplica en el desarrollo de software (ver figura 1). Si la
ejecución de una cierta tarea recae en una llamada de rutina que maneja sus sub-tareas, es
necesario especificar la relación entre el cliente (el que llama a la rutina) y el proveedor (la
rutina llamada) lo más preciso posible.
Los contratos de software se especifican con aserciones. El Diseño por contratos
contempla tres tipos de proposiciones:
Precondición
Poscondición
Invariante de clase
Figura 1. Especificación de un contrato de software.
12
CAPÍTULO 2
MARCO TEÓRICO
El principio de no-redundancia establece que “bajo ninguna circunstancia el cuerpo de
una rutina deberá checar el cumplimiento de la precondición”. La precondición compromete
al cliente ya que define las condiciones por las cuales una llamada a la rutina es válida. Las
poscondiciones comprometen a la clase (donde se implementa la rutina) ya que establecen las
obligaciones de esa rutina. Es muy importante notar que la precondición y la poscondición
que definen el contrato forman parte del elemento de software en sí. Es importante incluir las
precondiciones y poscondiciones como parte de la declaración de una rutina.
Nombre de la rutina (declaraciones de los argumentos) is
--Comentarios de cabecera
require
Precondición
ensure
Pos condición
end
Figura 2. Ejemplo de la declaración de una rutina en el lenguaje de programación Eiffel con
precondiciones y poscondiciones explícitas.
La figura 2 es un ejemplo de la notación Eiffel [9]. Cada aserción es una lista de
expresiones booleanas, separadas por “;”. El “;” es equivalente al “and” booleano, permite
identificadores individuales de las aserciones. La precondición expresa requerimientos que
cualquier cliente debe cumplir para que la transacción sea correcta. La poscondición expresa
propiedades que son aseguradas al momento de ejecutarse el procedimiento.
Es posible establecer unas aserciones más fuertes que otras. ¿Qué significa que una
aserción es más fuerte que otra? Lo anterior se define de la siguiente manera: dadas dos
aserciones P y Q, P es más fuerte o igual que Q si P implica Q, como es establecido en [4]. El
concepto de fortificar o debilitar aserciones se utiliza en la herencia cuando es necesario
redefinir rutinas.
En Eiffel (y en general en las herramientas disponibles para otros lenguajes) el
lenguaje para soportar aserciones tiene algunas diferencias con el cálculo de predicados, en
primer lugar no tiene cuantificadores (aunque el concepto de agentes provee un mecanismo
para especificarlos). Además soporta llamadas a funciones y existe la palabra reservada old
para indicar el valor anterior a la ejecución de la rutina de algún elemento.
Las invariantes de clase sirven para expresar propiedades globales de las instancias de
una clase, mientras que las precondiciones y poscondiciones describen las propiedades de
rutinas o funciones particulares. Desde el punto de vista de la metáfora de los contratos, las
invariantes de clase establecen regulaciones generales aplicables a todos los contratos.
Provienen del concepto de invariante de datos de [4] y deben satisfacerse en dos momentos de
la ejecución: después de la creación del objeto y después de la llamada a una rutina por un
cliente.
Existe una serie de principios a seguir cuando se diseñan clases utilizando contratos de
software [12]:
1. Separar consultas de comandos. Las rutinas de una clase deben ser (en lo posible) o
comandos o consultas pero no ambas cosas. Las consultas devuelven un valor (ej.
funciones) y los comandos pueden cambiar el estado interno de un objeto.
13
CAPÍTULO 2
MARCO TEÓRICO
2. Separar consultas básicas de consultas derivadas. La intención es conseguir un
conjunto de especificaciones formado por consultas que denominamos básicas, de
manera que el resto de las consultas puedan derivarse de las básicas.
3. Para cada consulta derivada escribir una poscondición especificando su resultado en
términos de una o más consultas básicas. Esto permite conocer el valor de las
consultas derivadas conociendo el valor de las consultas básicas. Idealmente, sólo el
conjunto mínimo de especificación tiene la obligación de ser exportado públicamente.
4. Para cada comando escribir una precondición que especifique el valor de cada
consulta básica. Dado que el resultado de todas las consultas puede visualizarse a
través de las consultas básicas, con este principio se garantiza el total conocimiento
de los efectos visibles de cada comando.
5. Para toda consulta o comando decidir una precondición adecuada. Este principio se
auto-explica ya que permite definir claramente el contrato de cada rutina.
6. Escribir el invariante para definir propiedades de los objetos.
Los contratos se categorizan en cuatro diferentes niveles [13]. Cada nivel de contrato
define otro contrato y la combinación de los cuatro define un contrato global. Los cuatro
diferentes niveles son los siguientes:
1.
2.
3.
4.
Contratos sintácticos, la firma de los tipos de datos.
Contratos de comportamiento, descripción semántica de los tipos de datos.
Contratos de sincronización, los que tratan con la concurrencia.
Contratos de calidad de servicio (QoS), todos los requerimientos no funcionales y
garantías.
El nivel sintáctico es provisto por el tipo de firmas de un servicio descrito por una
interface. Reglas comunes de subtipos permiten substitución. El contrato de comportamiento
se expresa en interfaces mediante aserciones. Los aspectos de sincronización de contratos aún
necesitan ser estudiados en el marco de componentes referentes a concurrencia. En QoS su
especificación se hace con QML (QoS Modeling Language) [14]. Existen tres mecanismos:
tipo de contrato, contrato propiamente dicho y su perfil.
Para combinar correctamente los cuatro niveles para definir un contrato global, se
debe determinar qué propiedades o características debe tener el contrato. Las tres mínimas
propiedades son: una especificación formal, una regla de conformidad (para permitir
substitución) y una técnica de monitoreo en tiempo de ejecución [15].
En [16] los autores definen un lenguaje de especificación de contratos. Contract
Definition Language (CDL) permite la descripción de atributos de un contrato (ver figura 3).
Se identifican diferentes tipos de contratos: contrato base que denota información básica
acerca del componente; contrato de método que describe los métodos del componente o
servicio; y contrato de evento que se usa para exponer eventos a los cuales otros componentes
pueden suscribirse.
Dentro del apartado method se especifica la información acerca de parámetros,
invocaciones, precondiciones, poscondiciones, invariantes de clase, eventos, clasificación de
aserciones y localización de los métodos. Para cada parámetro es posible definir: nombre,
tipo, restricción e inicialización. Las precondiciones, poscondiciones e invariantes de clase
comparten la misma estructura de especificación (ver figura 4).
14
CAPÍTULO 2
MARCO TEÓRICO
Figura 3. Atributos de un contracto en CDL [16].
En [16] los contratos se modelan como máquinas abstractas con el objetivo de
especificarlos formalmente y asegurar que sean correctos. Una máquina abstracta se
caracteriza por tener elementos dinámicos y estáticos. Los elementos estáticos son la
definición del estado y los dinámicos corresponden a las operaciones.
En el mapeo de CDL a máquinas abstractas los parámetros y las propiedades no
funcionales se convierten en variables de estado y los métodos corresponden a funciones de
estado. Las precondiciones, poscondiciones e invariantes se mapean directamente de CDL.
Para cada variable de estado se define su dominio.
15
CAPÍTULO 2
MARCO TEÓRICO
Figura 4. Estructura de especificación de un contrato [16].
2.6.2. Invariante de clase
Asegura que todos los objetos mantengan su integridad durante su ciclo de vida, sin importar
qué método se les aplique. Es una expresión booleana que especifica las condiciones bajo las
cuales un objeto se mantiene “bien definido”. Describe el estado interno del objeto usando las
variables públicas y las variables privadas de instancia de la clase. Una expresión INV es una
invariante correcta para la clase C si cumple con las siguientes condiciones:
Cada llamada a un constructor C con argumentos que satisfacen su precondición, crea
un nuevo objeto con un estado que satisface INV.
Cada llamada a un método M con argumentos que satisfacen su precondición, deja el
objeto en un estado que satisface INV.
Lo anterior quiere decir que la invariante de clase debe ser verdadera cuando el objeto es
creado por un constructor y debe permanecer así después de la llamada a cualquier método.
2.7. Correctitud en programación orientada a objetos
Utilizando la notación de terna de Hoare, se define formalmente “clase correcta” como: sea R
la invariante de clase, Pi la precondición y Qi la poscondición para el i-ésimo constructor Ci o
método Mi, entonces una clase es correcta respecto a sus aserciones si se tiene:
1. Para el conjunto de argumentos válidos x para cada constructor Ci, se cumple {Pi(x)}
Ci.body {Qi(x) ∧ INV}.
16
CAPÍTULO 2
MARCO TEÓRICO
2. Para el conjunto de argumentos válidos x para cada método Mi, se cumple {Pi(x) ∧
INV }Mi.body{Qi(x) ∧ INV}.
La regla 1 significa que la ejecución del constructor para cualquier objeto establece la
validez de la invariante de clase. La regla 2 dice que la ejecución de métodos en donde la
invariante de clase es válida, ésta se preserva así durante el transcurso y término de los
métodos.
2.8. Mecanización de la verificación de programas
Se han hecho varios intentos para automatizar la prueba de correctitud de programas mediante
sistemas automáticos que generan demostraciones formales en la lógica de Floyd-Hoare.
Desafortunadamente se ha demostrado que, en principio, es imposible diseñar un
procedimiento para decidir automáticamente la verdad o falsedad de una sentencia
matemática arbitraria.
Sin embargo, es posible probar la validez de una demostración formal. Esto consiste
en checar que los resultados de cada línea de la demostración son axiomas o consecuencias de
líneas anteriores. Las demostraciones de correctitud de programas son largas y tediosas por lo
que es común cometer errores cuando son generadas manualmente. Razón por la cual es útil
comprobar de manera automática su validez, aún cuando sólo puedan ser generadas mediante
el análisis humano.
Un sistema de verificación de demostraciones toma como entrada una especificación
de correctitud parcial de un programa. De las especificaciones anotadas en el programa el
sistema genera un conjunto de sentencias puramente matemáticas llamadas condiciones de
verificación (vc’s verification conditions). Las vc’s se verifican automáticamente en un
demostrador de teoremas.
Para demostrar que una terna de Hoare {P}S{Q} es correcta, se siguen tres pasos:
1. El programa S se anota (se insertan anotaciones). Esta parte es difícil y necesita buen
entendimiento del programa.
2. Se genera un conjunto de sentencias lógicas (vc’s) a partir de la especificaciones
anotadas. Este proceso es puramente mecánico y puede ser fácilmente realizado por un
demostrador de teoremas.
3. Las condiciones de verificación son probadas.
Si se demuestra {P}S{Q} entonces la terna ⊢{P}S{Q} es válida. El programa S es
correcto parcialmente y cumple con sus especificaciones.
A continuación se mostrará un ejemplo sencillo de cómo se generan las condiciones de
verificación manualmente.
Supóngase que se quiere demostrar el siguiente programa:
El primer paso es insertar las anotaciones, se tiene:
17
CAPÍTULO 2
MARCO TEÓRICO
El siguiente paso es generar las condiciones de verificación:
Las condiciones de verificación se generan al aplicar las reglas y axiomas de la lógica
de Hoare. Nótese que son sentencias aritméticas. El último paso consiste en probarlas en un
demostrador de teoremas, lo cual es una tarea relativamente sencilla.
2.9. Verificación de modelos
Otro enfoque de razonamiento formal de programas es la verificación de modelos (model
checking). Esta técnica se utiliza para demostrar que se mantengan algunas propiedades a
través del tiempo de un modelo formalmente especificado. El modelo puede ser alguno de los
siguientes:
Del programa (cada comando es un estado).
Una abstracción del programa.
Un modelo de la especificación.
Un modelo del dominio.
Verificación de modelos está basada en la lógica temporal lineal (LTL). Busca una
solución entre todos los posibles estados del modelo dado. Da respuesta a algunas preguntas
acerca de propiedades temporales de un programa.
Se construye el modelo A de un problema o sistema. L(A) denota todos los posibles
comportamientos y L(P) el conjunto de comportamientos válidos (i.e. aquellos donde la
propiedad P es satisfactible). Para demostrar que el modelo A siempre satisface P, es
suficiente mostrar que L(A) ⊆ L(P) o equivalentemente L(A) ∩ L(P) = ∅. Si la intersección
anterior es no vacía, se busca un contraejemplo al comportamiento válido.
2.9.1 Verificación de modelos temporal
En la mayoría de los sistemas, la verdad de algunas fórmulas es dinámica, es decir, cambia
con el tiempo.
Existen dos tipos de lógica temporal:
Árbol lógico de computación (computation tree logic CTL). El tiempo se representa
como un árbol que tiene raíz en el momento actual y se extiende hacia el futuro.
18
CAPÍTULO 2
MARCO TEÓRICO
Lógica temporal lineal (linear-time temporal logic LTL). El tiempo es un conjunto de
trayectorias. Una trayectoria es una secuencia de instantes en el tiempo. La lógica
temporal lineal se relaciona con la teoría de autómatas finitos, la cual se utiliza para
modelar sistemas.
2.9.2 Lógica temporal lineal (LTL)
La lógica temporal (LT) es una extensión de la lógica clásica y es usada para describir un
sistema de reglas y simbolismo, para la representación y razonamiento de proposiciones
cuantificadas en términos de tiempo. La lógica temporal lineal (LTL) modela el tiempo como
una secuencia de estados, que se extienden infinitamente. Esta secuencia de estados se conoce
como camino. En general el futuro no está determinado, por lo que se consideran diferentes
caminos que representan posibles futuros. LTL contiene a los siguientes operadores:
Siguiente
Eventualmente
Siempre
Fuerte hasta
Débil hasta
2.9.3. Sintaxis de LTL
Una fórmula bien formada (wff) en LTL se define recursivamente como:
⊤ y ⊥ son fórmulas bien formadas.
Si p es un símbolo proposicional que representa una propiedad falsa o verdadera en
cualquier estado del modelo A, p es una fórmula bien formada.
Si p y q son fórmulas bien formadas entonces también:
Son todas.
2.9.4. Semántica de LTL
El símbolo ⊨ expresa que una secuencia de estados satisface una fórmula bien formada en
LTL. Lo anterior significa que:
σ[i] ⊨ p ⇔ la propiedad p permanece en la secuencia de estados { σi, σi+1,…}.
σ ⊨ f ⇔ la fórmula temporal f permanece en la secuencia de estados σ.
Con esta notación se definen formalmente los operadores temporales de LTL a
continuación.
Siempre: σ ⊨ □p ⇔ ∀i ≥ 0. (σ[i] ⊨ p)
□p establece que la propiedad p permanece invariantemente verdadera a través de una
secuencia de estados.
Eventualmente: σ[i] ⊨ ◊p ⇔ ∃i ≥ 0. (σ[i] ⊨ p)
19
CAPÍTULO 2
MARCO TEÓRICO
◊p establece la garantía de que la propiedad p eventualmente se convertirá verdadera al menos
una vez.
Siguiente: σ[i] ⊨ p ⇔ σ[i+1] ⊨ p
p establece que la propiedad p es verdadera en el estado siguiente.
20
CAPÍTULO 3
ANTECEDENTES
Capítulo 3) ANTECEDENTES
En el presente capítulo se describe el antecedente de este trabajo de tesis, un estudio de la
evolución de las especificaciones formales y el análisis de algunos trabajos relacionados.
3.1. Antecedentes
La tesis que se presenta en este documento tiene como antecedente un trabajo de
investigación desarrollado por un estudiante del grupo de ingeniería de software del
CENIDET.
A continuación se describe la tesis de maestría que es el antecedente de este trabajo.
3.1.1. Identificación de funciones recurrentes en software legado [1]
El objetivo general de [1] fue definir un mecanismo de identificación de funciones
implementadas una y otra vez en diferentes aplicaciones aún con diferente comportamiento.
Dada su naturaleza recurrente significa que estas funciones representan componentes
reusables, por lo tanto podrían ser consideradas para incorporarlas a un marco de
componentes reusables para evitar su duplicación. Este tipo de funciones se identificaron en
software legado a partir del análisis de precondiciones y poscondiciones. Las precondiciones
y poscondiciones de funciones se convirtieron a predicados lógicos de primer orden y se
evaluaron con un demostrador automático de teoremas.
Los resultados obtenidos al evaluar el desempeño de la herramienta desarrollada en
este trabajo confirmaron que es posible implementar el análisis de precondiciones y
poscondiciones para determinar la recurrencia de funciones. Además de que existe una fuerte
21
CAPÍTULO 3
ANTECEDENTES
dependencia entre la especificación formal correcta de las precondiciones y poscondciones y
el nivel de desempeño de la herramienta propuesta en [1].
3.2. Estado del arte
A mediados de los años 60’s del siglo pasado apareció el primer trabajo sobre especificación
formal. Debido a que la complejidad de los sistemas se hizo creciente, surgió la necesidad de
tener un estándar riguroso para definir formalmente el significado de programas. Esto con el
objetivo de realizar pruebas sobre ciertas propiedades, particularmente de la forma: si los
valores iniciales de las variables de un programa satisfacen la relación R1, entonces los
valores finales una vez terminada su ejecución, satisfacen a la relación R2 [2].
Para demostrar las propiedades mencionadas anteriormente, se hizo evidente la
necesidad de contar con un sistema lógico formal. Es cuando se desarrolló la lógica de Hoare
[4] para hacer razonamiento válido acerca de programas. En esta lógica se utilizan axiomas y
reglas de inferencia como en cualquier sistema deductivo.
Es en este punto donde surge un nuevo problema: encontrar aserciones adecuadas a
cada estado del programa que se está analizando. Se han propuesto diversos métodos para
generar aserciones semiautomática o automáticamente [3]. Un ejemplo de las técnicas usadas
es el uso de ecuaciones diferenciales finitas para caracterizar la acción de las variables al
ejecutar el control de un ciclo. En algunos casos significativos, las ecuaciones diferenciales
pueden ser resueltas ya sea de manera manual o con la ayuda de un sistema deductivo
mecánico. Las aserciones también se pueden determinar con técnicas heurísticas.
Poco tiempo después, surge una estrategia para la deducción de precondiciones basada
en la lógica de Hoare conocida como el método de la precondición más débil (weakest
precondition) wp(S,R) [5]. El transformador de predicados wp(S,R) es una función de mapeo
entre una lista de comandos S y un predicado R (poscondición). Esta función da como
resultado la precondición más débil para S (S termina) con R verdadero. Análogamente existe
otro transformador de predicados de poscondición más fuerte (strongest postcondition) [6].
Establece que “dada una precondición Q y una secuencia de comandos S, sp deriva un
predicado sp(S,Q)”.
Al mismo tiempo que se exploran técnicas para la generación de invariantes aparece
Ejecución simbólica [7]. Es otra herramienta para deducción de propiedades de programas. En
vez de entradas triviales para un programa (por ejemplo números), se utilizan símbolos que
representan valores arbitrarios. La ejecución procede de manera normal, excepto que los
valores pueden ser fórmulas simbólicas. El resultado obtenido es un árbol de ejecución que
caracteriza los caminos tomados durante la ejecución simbólica de un programa. En el árbol
que se obtiene, se asocia un nodo con cada comando ejecutado etiquetado con el número en la
secuencia de la ejecución que le corresponde. Cada transición entre comandos es un arco
dirigido que conecta los nodos asociados. Este método tiene como ventaja que puede
representar una clase infinita (en la mayoría de los casos) de ejecuciones normales.
Conforme la programación orientada a objetos ganó terreno en el mundo de
desarrollo de software, sus usuarios vieron la necesidad de contar con una metodología
estructurada con el objetivo de desarrollar software de una manera confiable, robusta y con
correctitud. Ésta metodología es conocida como Diseño por contratos [8].
Se vuelve evidente la necesidad de contar con lenguajes de especificación formal que
puedan ser embebidos en el código fuente para su verificación. Eiffel [9] es uno de los pocos
lenguajes que ofrece soporte nativo para aserciones, sin embargo otros lenguajes como Java
[10] y C# [11] cuentan con extensiones para la especificación formal.
El problema central que prevalece desde los inicios de la especificación formal, es la
deducción de invariantes de ciclo. La generación de invariantes de ciclo ha sido estudiada
desde los inicios de la especificación formal y se han propuesto diversos métodos para su
generación semiautomática o automáticamente. Un ejemplo, es el uso de ecuaciones
22
CAPÍTULO 3
ANTECEDENTES
diferenciales finitas para caracterizar la acción de las variables al ejecutar el control de un
ciclo. Otro ejemplo es la generación automática de invariantes de ciclo usando la técnica de
suma simbólica (symbolic summation) combinada con álgebra polinomial [23].
Actualmente se encuentran en desarrollo técnicas y herramientas que tienen como
propósito la extracción de especificaciones formales a partir de código fuente, ejemplo de ello
son las herramientas Discern [20] y Daikon [21].
3.3. Trabajos relacionados
Mantener y especificar contratos es una tarea costosa en cuanto a tiempo y esfuerzo para los
desarrolladores. La gran mayoría del código fuente no cuenta con la especificación de su
contrato. Por lo que surge el problema de extraer contratos después del ciclo de desarrollo de
software. Algunos trabajos se enfocan al análisis del código fuente (análisis estático)
mientras que otros analizan los resultados obtenidos después de una serie de ejecuciones
experimentales (análisis dinámico). Finalmente, otros combinan ambos enfoques para obtener
mejores resultados.
3.3.1. Análisis de código fuente
Es posible extraer algunas especificaciones por simple inspección de código fuente. Aunque
no se deducen todos los elementos que constituyen un contrato siguiendo esta técnica, la
información recabada es valiosa. A partir de ella se pueden deducir las aserciones faltantes. A
continuación se describen dos trabajos que utilizan esta técnica.
3.3.1.1. Extended Static Checking for Java ESC/Java [17]
En este trabajo se presentó la herramienta ESC/Java que permite encontrar errores comunes
en programas. ESC/Java tiene un lenguaje de anotación semejante a Java para expresar
formalmente el diseño. ESC/Java examina el código anotado y genera condiciones de
verificación utilizando técnicas de demostración automática de teoremas. La salida de esta
herramienta es un conjunto de advertencias de inconsistencias entre diseño y código.
Los programadores que probaron ESC/Java reportaron que la herramienta aun no ha
alcanzado el nivel deseado de efectividad. En particular, da una excesiva cantidad de
advertencias sin fundamento en códigos sin anotación o parcialmente anotados. Lo anterior
significa que los programadores deben proveer anotaciones iníciales y no es una herramienta
completamente automática.
3.3.1.2. Houdini, An annotation assistant for ESC/Java [18]
El objetivo de este trabajo es ayudar a reducir el costo de escribir especificaciones formales
para los programadores. La herramienta Houdini es un asistente para la anotación de código
fuente. Mediante un exhaustivo análisis al código fuente, genera un gran número de
candidatos a anotaciones y utiliza ESC/Java para verificar o refutar, cada uno de los
candidatos a anotación.
Para el uso de esta herramienta se recomienda como estrategia mantener el código
base con unas cuantas anotaciones insertadas manualmente. Una limitante de Houdini es que
no puede inferir todas las anotaciones relevantes. Además, el programador debe proveer
anotaciones iníciales.
3.3.2. Análisis de documentación
El primer lugar donde se comienza a buscar información acerca de especificaciones formales
es la documentación. Es posible inferir algunas aserciones si se cuenta con una
documentación adecuada del código fuente. Algunas etapas de este tipo de análisis pueden ser
automatizadas. A continuación se presenta un trabajo que describe el análisis de
23
CAPÍTULO 3
ANTECEDENTES
documentación en lenguajes orientados a objetos, así como la experiencia obtenida en su
estudio y las lecciones aprendidas.
3.3.2.1. Uncovering hidden contracts: The .NET example [19]
En [19] se analizó la colección de librerías de estructuras de datos y algoritmos en la .NET
Framework Class Library. Fue posible extraer automáticamente algunos elementos que
intervienen en su especificación formal. También se detectaron algunos patrones en el código
y la documentación que apuntan a posibles contratos.
La justificación que los autores dieron de porqué utilizaron librerías de componentes
de reuso en .NET es porque están desarrolladas bajo el concepto de Metadata. Éste brinda
información substancial acerca de su especificación y provee documentación que hace auto
descriptivo cada componente al estilo de diseño por contratos.
Después de realizar pruebas con la clase ArrayList que pertenece a esta librería
encontraron como resultado que las poscondiciones de las rutinas se encuentran expresadas en
.NET pero no de manera explícita. Es necesario el análisis humano en el análisis ya que estas
poscondiciones se encuentran dispersas en toda la documentación. Las precondiciones tienen
posibilidad de inferirse automáticamente a partir de excepciones. Las clases que implementan
interfaces comparten un conjunto de propiedades que podrían ser candidatas a invariantes de
interface, pero el análisis a la documentación no reveló dichas propiedades.
3.3.3. Análisis dinámico
El análisis dinámico consiste en realizar una serie de ejecuciones experimentales de un
programa con el propósito de inferir algunas propiedades. A continuación se presenta un
trabajo que utiliza este tipo de análisis para extraer especificaciones formales a partir de
código fuente.
3.3.3.1. The Daikon system for dynamic detection of likely invariants [21]
En este trabajo se describe la herramienta Daikon. Esta herramienta es la implementación de
detección dinámica de invariantes (dynamic detection of likely invariants). La salida de
Daikon es un conjunto de posibles invariantes que son justificadas estadísticamente por
rastreo de ejecuciones. Primero descubre invariantes de ejecuciones mediante la
instrumentación del programa objetivo para el rastreo de ciertas variables. Después se ejecuta
el programa instrumentado y se infieren las invariantes de ambos programas (instrumentado y
no instrumentado).
La tarea básica del instrumentador es agregar instrucciones al programa objetivo para
rastrear los valores de salida de las variables. El resultado es enviado directamente a la
máquina de inferencias (inference engine) para la inferencia de las invariantes. La máquina
de inferencias lee los datos producidos por el instrumentador y produce posibles invariantes.
Utiliza un algoritmo genera y prueba (generate and check) para probar un conjunto de
potenciales invariantes contra los valores de el rastreo de las variables. La precisión de los
resultados depende en gran parte de la calidad de los casos de prueba lo cual es una
importante limitante.
3.3.4. Análisis de documentación y análisis dinámico
Los trabajos que se describen a continuación combinan ambas técnicas para obtener mejores
resultados en la extracción de especificaciones formales a partir de código fuente.
24
CAPÍTULO 3
ANTECEDENTES
3.3.4.1. Extracting functional and Non Functional Contracts from Java Classes
and Enterprise Java Beans [16]
El trabajo presentado en [19] motivó a otros para analizar software escrito en otros lenguajes
de programación para encontrar su especificación formal.
El objetivo de este trabajo es analizar código fuente escrito en Java y su
documentación. Primero, se analiza la documentación para encontrar candidatos a
especificaciones. Después, se construye un modelo de ejecución del programa (i.e. posibles
valores de las variables) como máquina abstracta. Finalmente, se monitorean los cambios y
se infiere el contrato.
Para encontrar una lista inicial de candidatos a anotación se analiza la documentación.
Las precondiciones se extraen de las condiciones de los métodos que checan los parámetros
de entrada y excepciones. En un lenguaje que soporta manejo de excepciones las
precondiciones generalmente van ligadas a ellas. Las poscondiciones se encuentran en la
documentación y los comandos return de cada método. Se usan comentarios en Javadoc para
extraer información de contratos de clases de Java. Las etiquetas @throws y @exception son
de ayuda para extraer precondiciones. De @param se extraen firmas de métodos y candidatos
a precondiciones. Si un constructor está comentado con alguna de estas etiquetas, se utiliza
esta información para inferir invariantes. La etiqueta @return es punto de partida para formar
candidatos a poscondiciones.
Después de completar el paso anterior se refinan los candidatos con análisis estático.
Mediante una inspección al código fuente se prueba que las aserciones identificadas son
correctas. Para lo anterior, se asume la negación de las aserciones y se prueba que no es
posible o no se cubren todos los posibles estados modelados con el candidato asumido. En
este paso se podrían identificar candidatos adicionales y se construye un contrato temporal.
El siguiente paso es evaluar los candidatos usando una función de aptitud, si no son
válidos se ejecuta análisis dinámico con otro conjunto de pruebas. Una vez que los candidatos
han sido evaluados y validados se proponen como contrato final.
3.3.4.2. Discern: Towards the automatic discovery of software contracts [20]
En este trabajo se propone un método para deducir semiautomáticamente especificaciones
formales y se encuentra aún en fase de implementación.
Recurre al análisis estático para descubrir posibles contratos. Una de las dificultades
en este tipo de análisis son los ciclos, pero se utilizan heurísticas para reconocer sus formas
más comunes y extraer información útil de ellos.
La herramienta Discern utiliza un conjunto de especificaciones de librerías integradas
en los lenguajes de programación más usados.
Además, utiliza los métodos formales de precondición más débil y poscondición más
fuerte en la deducción de especificaciones formales. Internamente representa el programa con
un lenguaje de especificación y utiliza demostradores automáticos de teoremas para la
validación de las condiciones de verificación.
La limitante de este trabajo es que el análisis requiere de ayuda humana ya que
Discern deriva posibles contratos con la adición manual de una poscondición y el etiquetado
de tres aserciones como invariantes.
25
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
Capítulo 4) ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
Para extraer especificaciones formales (contratos) a partir de código fuente orientado a
objetos, es necesario el análisis humano. Algunas partes de la tarea son relativamente fáciles
pero otras resultan ser complejas, como el caso de la deducción de invariantes de ciclo.
Además, se requiere amplio conocimiento del problema y su dominio, lo cual es otra limitante
ya que por lo general, quien analiza el código fuente para deducir especificaciones formales
no lo diseñó originalmente. Es posible automatizar algunas partes del proceso, pero depende
del lenguaje en el que se encuentre implementado el código fuente.
En este capítulo se presenta el análisis realizado a las diferentes propuestas en los
trabajos relacionados. Se identificaron las técnicas y herramientas identificadas que pueden
ayudar en el proceso de extraer especificaciones formales a partir de código fuente, las cuales
se enlistan a continuación:
1.
2.
3.
4.
5.
Análisis de documentación
Análisis de código fuente
Lógica de Hoare y cálculo de la precondición más débil wp
Análisis dinámico
Generación automática de invariantes.
A partir de este análisis se construyó un algoritmo de decisión para realizar la
extracción de especificaciones formales. Además, este algoritmo se demostró formalmente
con la técnica automática de verificación de modelos.
26
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
4.1. Técnicas y herramientas que pueden ayudar en la extracción de
especificaciones formales
4.1.1. Análisis de documentación
Javadoc [22] es un generador automático de documentación de código fuente escrito en Java.
La documentación generada está en formato HTML. Javadoc no tiene semántica formal y no
puede ser verificado formalmente. Se utilizan los caracteres /** . . . */ para indicar al
compilador que es una anotación en Javadoc. Cuenta con palabras reservadas como @param
y @return.
Si se analiza la documentación generada con Javadoc, es posible extraer información
implícita de especificaciones formales de clases. No es posible desarrollar una herramienta
formal para analizar automáticamente la documentación, por lo que es imprescindible la
intervención humana.
Las precondiciones se extraen de las anotaciones fe las condiciones que verifican los
parámetros de entrada y en los comandos que provocan la ejecución de una excepción. Las
precondiciones generalmente van ligadas a las excepciones. Las precondiciones se deducen
del comando try...catch (es posible automatizar este paso). Las poscondiciones se buscan en
las anotaciones del comando return de cada método.
A continuación se enlistan las etiquetas de Javadoc de las cuales se extrae información
relevante
1. @throws y @exception. Contienen información acerca de precondiciones, se identifican
excepciones que se encuentran en métodos.
2. @param. Se refiere a firmas de métodos y precondiciones.
3. @return. Provee información acerca de poscondiciones.
4. @see. Con la información extraída de esta etiqueta, se analiza la herencia y dependencia
en el comportamiento de una clase, para verificar si existen conflictos entre las
precondiciones, poscondiciones e invariantes identificadas.
Si un constructor esta comentado con cualquiera de las etiquetas: @throws,
@exception y @param, es posible que contenga información referente a la invariante de
clase.
En [19] se describe la extracción de información referente a especificaciones formales
de programas escritos en .NET.
4.1.2. Análisis de código fuente
Cuando la documentación no existe o no es suficientemente explícita, se analiza el código
fuente para encontrar elementos que pudieran estar implícitos en él. Posibles fuentes de
invariantes son: constructores; interfaces implementadas y clases base.
Las precondiciones usualmente están ligadas a las excepciones (si el lenguaje lo
soporta). Se consideran como precondiciones las condiciones en un método en las cuales no
se lanzan excepciones.
Las poscondiciones se encuentran implícitas en el comando return. En este paso es
imprescindible el análisis humano ya que es necesario conocer el dominio del problema para
poder inferir poscondiciones a partir del comando return.
4.1.3. Lógica de Hoare y cálculo de la precondición más débil wp
No es posible automatizar algunas etapas del cálculo de la precondición más débil wp y la
lógica de Hoare debido a razones prácticas y teóricas. Debido a que el usuario debe proveer
27
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
algunas aserciones como invariantes de ciclo y poscondiciones. Sin embargo, la generación de
condiciones de verificación y su prueba en un demostrador de teoremas si es automatizable.
4.1.4. Herramientas disponibles en Internet
A continuación se presentan dos herramientas de código libre que se encuentran disponibles
en Internet y que pueden ser de ayuda en la extracción de especificaciones formales a partir de
código fuente:
4.1.4.1. Análisis dinámico
La herramienta Daikon [21] es una implementación de análisis dinámico. Detecta en tiempo
de ejecución, posibles precondiciones, poscondiciones e invariantes de código fuente escrito
en los lenguajes de programación: C; C++; Java; Perl; entre otros. La salida de Daikon es un
conjunto de posibles especificaciones formales que son justificadas estadísticamente por
rastreo de ejecuciones. La figura 5 es un ejemplo de salida que contiene posibles
especificaciones para una implementación de la estructura de datos pila.
Figura 5. Ejemplo de salida de Daikon.
Las aserciones encontradas en la figura 5 son candidatas a la especificación formal de
un contrato. Es necesario demostrar su validez formalmente. La manera de verificar
manualmente la validez de aserciones es mediante la lógica de Hoare y el cálculo de la
precondición más débil wp.
4.1.4.2. Generación automática de invariantes
ALIGATOR [23] es un paquete de la herramienta Mathematica [24], su función es razonar
algebraicamente sobre una amplia clase de ciclos imperativos llamados P-solvable (tiene
solución). Estos ciclos contienen asignaciones, secuencias y condicionales.
ALIGATOR combina symbolic summation (suma simbólica) y álgebra polinomial. Su
salida es un conjunto finito de invariantes que es base para formar el ideal de invariantes.
Contiene rutinas para:
Verificar que un ciclo sea P-solvable.
Transformar ciclos en un sistema de ecuaciones recurrentes.
Resolver recurrencias y derivar formas cerradas de invariantes de ciclo.
28
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
Computar el ideal de invariantes.
Esta herramienta es útil en la búsqueda de invariantes de ciclo, cuando las heurísticas
comúnmente utilizadas son ineficaces.
4.2. Etapas del problema de la extracción de especificaciones formales
De acuerdo al análisis realizado a los trabajos relacionados con este problema, existen tres
tareas en la extracción de precondiciones, poscondiciones e invariantes de código fuente.
Éstas son independientes del lenguaje en el que esté implementado el código fuente. En la
figura 6 se mencionan las tareas
Analizar
documentación y
código fuente
Deducir
especificaciones
formales
Verificar correctitud de
especificaciones formales
deducidas
Figura 6. Tareas de la extracción de especificaciones formales.
Primero se analiza la documentación y código fuente en busca de precondiciones,
poscondiciones e invariantes.
Para la deducción de especificaciones formales es necesario contar con poscondiciones
para cada método e invariantes de ciclo, las cuales deben anotarse en el código fuente.
Después se deducen las especificaciones formales. La deducción de precondiciones y
poscondiciones mediante wp se realiza manualmente a “lápiz y papel” como las
demostraciones matemáticas tradicionales. Este cálculo es complejo y tedioso, además de que
requiere amplio conocimiento del dominio del problema.
Al finalizar la generación de especificaciones ya sea de forma manual o automática, se
debe anotar el programa manualmente en algún lenguaje de especificación.
Por último se verifica que las especificaciones formales generadas sean correctas. A partir
del código anotado se generan las condiciones de verificación para ser demostradas. Al
término de este proceso se anotan las especificaciones correctas.
En la tabla 1 se presenta un resumen del análisis de las técnicas y herramientas que
ayudan en el proceso de deducir especificaciones formales, contrastando su capacidad de
automatización. Además se especifica el producto esperado de cada uno de ellos:
29
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
Tabla 1. Capacidad de automatización de técnicas y herramientas.
Técnica o herramienta
Automatizable
Producto
Análisis de documentación
Parcialmente
Pre/Pos/Inv clase
Análisis de código fuente
Parcialmente
Pre/Pos/Inv clase
Lógica de Hoare y wp
Parcialmente
Pre
Análisis dinámico
Si
Pre/Pos
Generación de invariantes
Parcialmente
Inv ciclo
Verificación de correctitud de
Si
Contrato correcto
contratos extraídos
Como conclusión del análisis anterior se tiene que no es posible la construcción de un
sistema completamente automatizado a partir de las técnicas y herramientas existentes.
Debido a esto fue necesario deducir un algoritmo que ayude en la elección de técnicas o
herramientas a utilizar para la extracción de especificaciones formales a partir de código
fuente.
4.3. Solución propuesta
La figura 7 muestra el algoritmo de decisión propuesto. El algoritmo incluye las herramientas
y técnicas listadas anteriormente. Los pasos del algoritmo están enumerados para efectos
ilustrativos, pero no son necesariamente ejecutados todos o secuencialmente. La entrada del
algoritmo es un código fuente S (puede ser un programa completo o una fracción de él) que ha
sido previamente compilado y su documentación D, en caso de que exista. La salida es el
contrato {P}Q{S} extraído de la documentación o deducido del código fuente, si el algoritmo
tiene éxito. Después de la extracción del contrato, éste se valida generando las condiciones de
verificación correspondientes.
Paso 1. Evaluación de la documentación.
El lugar más lógico para comenzar la búsqueda de especificaciones formales es la
documentación. Esta tarea requiere de esfuerzo humano y de decisiones subjetivas, ya que se
evalúa la existencia y la calidad de la documentación. La mayoría del tiempo el código
fuente no se encuentra debidamente documentado y si lo está quizá no sea lo suficientemente
explícito. Si la documentación cuenta con suficientes anotaciones explícitas, se continúa al
paso 2, en caso contrario al paso 4.
Paso 2. Análisis de la documentación.
Después de que se ha evaluado la documentación y se considera que cuenta con suficiente
información, se realiza un análisis estático. No es posible desarrollar una herramienta que
automatice completamente esta tarea. Es posible encontrar algunos patrones que apunten a
posibles contratos [19]. La documentación asociada también se analiza. Ésta incluye:
procedimientos; algoritmos; documentación del dominio; reglas del negocio; manuales de
operación; entre otros. De aquí se sigue al paso 3.
Paso 3. Evaluación de especificaciones extraídas de la documentación.
Un contrato está “completo” si contiene precondiciones, poscondiciones para todos los
métodos e invariantes de clase. Esta tarea requiere de decisiones subjetivas. Si las
especificaciones extraídas de la documentación son suficientes se considera como éxito. Por
el contrario, si no son suficientes se sigue al paso 4.
Paso 4. Análisis de código fuente.
Cuando la documentación no contiene información acerca de especificaciones, se analiza el
código fuente. Los elementos que por lo general tienen información implícita acerca de
30
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
invariantes de clase son: constructores; interfaces implementadas; y clases base. Las
precondiciones se encuentran implícitas en las excepciones y las poscondiciones en las
sentencias return. Una vez analizado el código fuente se continúa al paso 5.
Paso 5. Deducción de especificaciones.
Es posible utilizar la lógica de Hoare y el cálculo de la precondición más débil wp para
deducir especificaciones finales, si se extrajeron algunas especificaciones de la
documentación y el código fuente. Las invariantes de ciclo se pueden generar
automáticamente o encontrar con las heurísticas propuestas. En el caso de que no se haya
extraído ninguna información significativa acerca de especificaciones, se realiza análisis
dinámico para encontrar aserciones. Al terminar la deducción de especificaciones se procede
al paso 6.
Paso 6. Correctitud de las especificaciones deducidas.
Es necesario verificar que las especificaciones deducidas son correctas. Lo anterior se realiza
generando condiciones de verificación y demostrando su validez automáticamente en un
demostrador de teoremas. Si se tiene éxito en esta tarea, las especificaciones finales son
correctas parcialmente y por lo tanto formales. En caso contrario no se dedujeron
especificaciones correctas.
Figura 7. Algoritmo propuesto para la extracción de especificaciones formales.
31
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
4.4. Formalización de la solución propuesta
Se utilizó el método formal verificación de modelos para verificar que el algoritmo propuesto
es correcto (i.e. termina). El verificador de modelos que se utilizó es la herramienta Spin [25].
Ésta utiliza el lenguaje de programación Promela (Process Meta Language) [26] para
especificar algoritmos. Los pasos para verificar un algoritmo en Spin son los siguientes:
1.
2.
3.
4.
Se codifica el algoritmo en el lenguaje Promela (similar al lenguaje C).
Spin mapea automáticamente el algoritmo a un autómata finito.
El estado del algoritmo no deseado (never claim) se expresa en lógica temporal (LTL).
Spin traduce a un autómata la fórmula en LTL para el algoritmo y se verifica el
algoritmo.
Spin verifica que el lenguaje del autómata del algoritmo está contenido en el lenguaje del
autómata de la propiedad a verificar. Si se cumple lo anterior el algoritmo es correcto. En caso
contrario, Spin muestra la secuencia de líneas de código que contiene el error.
De acuerdo con el proceso anterior, primero se codificó el algoritmo propuesto en el
lenguaje Promela (ver anexo A). Spin genera internamente el autómata finito a partir del
código en Promela (la figura ejemplo de este automáta se encuentra en el anexo A).
Después se especificó la propiedad a verificar del algoritmo (si termina) como:
“¿siempre ocurre que eventualmente se alcanza el estado de éxito o no éxito?”. El
comportamiento deseado se expresa en LTL con la fórmula ◊(estado== exito ∨
estado==no_exito). El comportamiento no deseado (never claim) que es verificado por Spin
es la negación de la fórmula anterior y se expresa en LTL como □(estado!=éxito ∧
estado!=no_exito).
En la figura 8 se muestra el código del never claim generado por Spin en el lenguaje
Promela del comportamiento no deseado del algoritmo. Spin toma como entrada la fórmula
en LTL □(estado!=éxito ∧ estado!=no_exito) y la mapea al lenguaje Promela.
Figura 8. Código del never claim generado en Promela.
Spin efectuó automáticamente el último paso para la verificación del algoritmo. Por lo
tanto se demostró que no se alcanza el estado no deseado en cualquier ejecución del
algoritmo, por lo tanto el algoritmo termina (i.e. es correcto). La figura 9 muestra la salida de
Spin para el algoritmo propuesto, donde se observa que toma como entrada el código del
modelo y del never claim. Para el modelo del algoritmo propuesto Spin reportó que tiene 0
errores. El espacio de búsqueda contiene 71 estados en donde se verifica que no ocurra el
comportamiento no deseado del algoritmo.
32
CAPÍTULO 4
ANÁLISIS DEL PROBLEMA Y SOLUCIÓN
Figura 9. Salida de Spin para el algoritmo propuesto.
33
CAPÍTULO 5
PRUEBAS
Capítulo 5) Pruebas
Para mostrar el funcionamiento del algoritmo de decisión en la deducción de especificaciones
correctas y que el resultado de su ejecución corresponde a lo modelado, se realizaron cuatro
casos de prueba. El primero es un conocido caso de estudio que ilustra el principio de
substitución de Liskov: el problema Square-Rectangle [27]. Los siguientes dos casos son
tomados de [1]. El último es un caso donde el código se encuentra debidamente documentado.
En este capítulo se presentan los casos de prueba, los resultados obtenidos y el análisis
que se realizó en base a ellos.
5.1. Caso de prueba 1
Supóngase que se tiene el siguiente código fuente [28]:
class Rectangle
{
double itsHeight, itsWidth;
public:
Rectangle(double h, double w)
{
itsHeight = h;
itsWidth = w;
}
virtual void SetHeight(double h)
{
itsHeight = h;
}
virtual void SetWidth(double w)
34
CAPÍTULO 5
PRUEBAS
{
itsWidth = w;
}
double double GetHeight() const
{
return itsHeight;
}
double GetWidth() const
{
return itsWidth;
}
};
class Square: public Rectangle
{
public:
Square(double w):Rectangle(h, h)
{
}
virtual void SetHeight (double h)
{
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
virtual void SetWidth(double w)
{
Rectangle:: SetHeight(w);
Rectangle:: SetWidth(w);
}
};
Primero se evalúa la existencia de documentación (Paso 1). En este caso en particular
no existe documentación alguna que acompañe al código fuente. El código fuente no tiene
comentarios.
En la documentación asociada (teoría elemental de geometría) se encuentran algunos
elementos del contrato (Paso 2). Es bien conocido que el alto y ancho de un rectángulo no
necesariamente tienen el mismo valor. En el caso de un cuadrado, su alto y ancho si deben
tener el mismo valor. Por lo tanto, las invariantes de las clases Rectangle y Square son las
siguientes:
class Rectangle
//invariant itsHeight != itsWidth
class Square: public Rectangle
//invariant itsHeight = = itsWidth
Todos los posibles estados de los objetos tipo Rectangle deben mantener la propiedad
itsWidth != itsHeight y los objetos tipo Square conserva itsWidth = = itsHeight. Nótese que
estas invariantes de clase no están implícitas en el código fuente. Los dos elementos del
contrato extraídos hasta este punto (invariantes) son insuficientes (Paso 3).
Para encontrar los elementos faltantes se inspecciona el código fuente (Paso 4). En
esta etapa las precondiciones y poscondiciones para cada método son deducidas. La
precondición para el constructor de la clase Rectangle, es que los parámetros que toma como
35
CAPÍTULO 5
PRUEBAS
entrada (h y w) deben existir y tener valores reales, para evitar que se lance una excepción. La
poscondición para el constructor de la clase Rectangle es itsHeight = = h y itsWidth = = w ya
que son los valores esperados de cómputo. La deducción de precondiciones y poscondiciones
para los demás métodos de la clase Rectangle se extraen de manera similar. A continuación se
muestra el código con los elementos del contrato deducidos y anotados en el código fuente:
class Rectangle
//invariant itsHeight != itsWidth
{
public:
//requires exists: double h, double w
//ensures itsHeight = =h and itsWidth = = w
Rectangle(double h, double w)
{
itsHeight = h;
itsWidth = w;
}
//requires exists: double h
//ensures itsHeight = =h
virtual void SetHeight(double h)
{
itsHeight = h;
}
//requires exists: double w
//ensures itsWidth = = w
virtual void SetWidth(double w)
{
itsWidth = w;
}
//requires true
//ensures result = = itsHeight
double double GetHeight () const
{
return itsHeight;
}
//requires true
//ensures result = = itsWidth
double GetWidth () const
{
return itsWidth;
}
};
Se sigue el mismo proceso para la clase Square ya que hereda los atributos y métodos
de la clase Rectangle. Las precondiciones, poscondiciones e invariantes se anotan en el código
fuente:
class Square: public Rectangle
//invariant itsHeight = = itsWidth
{
public:
36
CAPÍTULO 5
PRUEBAS
//requires exists: h
//ensures itsHeight = =h and itsWidth = = h
Square(double w):Rectangle(h, h)
{
}
//requires exists: h
//ensures itsHeight = =h and itsWidth = = h
virtual void SetHeight (double h)
{
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
//requires exists: w
//ensures itsHeight = =w and itsWidth = = w
virtual void SetWidth(double w)
{
Rectangle:: SetHeight(w);
Rectangle:: SetWidth(w);
}
};
La siguiente tarea (Paso 6) contiene una decisión subjetiva: ¿se demuestra la
correctitud parcial del contrato deducido ó se considera el proceso terminado? Como no
existen estructuras complejas en el código fuente tales como while o if, no es necesario
utilizar la lógica de Hoare y el cálculo de la precondición más débil wp. Por lo tanto se
consideran completos los elementos del contrato deducidos. En las tablas 2 y 3 se resumen los
elementos del contrato extraídos para la clase Rectangle y la clase Square.
Tabla 2. Especificación formal de la clase Rectangle.
Método
Precondición
Poscondición
Rectangle
//requires exists:
//ensures itsHeight =
double h, double w
=h and itsWidth = = w
SetHeight
//requires exists:
//ensures itsHeight =
double h
=h
SetWidth
//requires exists:
//ensures itsWidth = =
double w
w
GetHeight
//requires true
//ensures result = =
itsHeight
GetWidth
//requires true
//ensures result = =
itsWidth
Tabla 3. Especificación formal de la clase Square.
Método
Precondición
Poscondición
Square
//requires exists: h
//ensures itsHeight =
=h and itsWidth = = h
SetHeight
//requires exists: h
//ensures itsHeight =
=h and itsWidth = = h
SetWidth
//requires exists: w
//ensures itsHeight =
=w and itsWidth = = w
Invariante de clase
//invariant itsHeight !=
itsWidth
Invariante de clase
//invariant itsHeight =
= itsWidth
Considérese ahora una nueva función, conocida como the real problem of the SquareRectangle problem [27]:
void g(Rectangle &r)
{
r.SetWidth(5);
37
CAPÍTULO 5
PRUEBAS
r.SetHeight(4);
assert(r.GetWidth() * r.GetHeight()) == 20);
}
La función anterior toma como parámetro de entrada un objeto de tipo Rectangle y le
da valores a sus atributos. El código fuente no tiene documentación que ayude a deducir sus
especificaciones pero el comando assert provee información importante para la deducción de
especificaciones.
Es posible utilizar las especificaciones encontradas anteriormente. Se analiza el código
fuente (Paso 4) comenzando por el último comando. Es sabido que el método SetHeight
requiere la existencia del parámetro que toma como entrada. Continuado con el siguiente
comando hacia arriba, se observa que el método SetWidth también requiere de la existencia
del parámetro de entrada. De lo anterior se deduce que la precondición del método g es que el
objeto r exista (r es no nulo y es un rectángulo).
El método g funciona sin problemas para un objeto de tipo Rectangle. El comando
assert declara un error si se pasa como parámetro un objeto de tipo Square y en este caso se
viola la invariante de clase itsHeight = = itsWidth. Se deduce que el contrato extraído para el
método g es incorrecto. En este caso particular el contrato es incorrecto por un mal diseño de
la clase y no es necesario seguir el proceso otra vez más.
5.2. Caso de prueba 2
En [1] se presenta el siguiente código que calcula el factorial de un número entero cualquiera:
int factorial (int n)
{
int i;
int prod=1;
if(n>1)
for(i=2;i<=n;i++)
prod*=i;
return(prod);
}
No existe documentación para el código fuente anterior (Paso 1). Se analiza el código
fuente (Paso 4) y se observa que la función regresa el valor de la variable prod. Esta variable
siempre tendrá valor positivo ya que en el ciclo se incrementa en cada iteración. Además, al
ser el parámetro de entrada un valor de tipo integer, se garantiza que siempre habrá al menos
una iteración del ciclo for. Debido a lo anterior se deduce que la poscondición de la función
factorial es prod>0.
El siguiente paso es utilizar la lógica de Hoare y el cálculo de la precondición más
débil wp para deducir las demás especificaciones (Paso 5). El cálculo se realiza siguiendo la
siguiente serie de pasos
1. Se debe reescribir parte del código para aplicar el cálculo de la precondición más débil
wp, ya que no existe una regla de inferencia definida en la lógica de Hoare para el
comando for. El código queda de la siguiente manera:
int factorial (int n)
{
int i;
int prod=1;
if(n>1)
{
i=2;
38
CAPÍTULO 5
PRUEBAS
while(i<=n;i++)
{
prod*=i;
i++;
}
}
return(prod);
}
2. Se calcula ahora la precondición más débil comenzando por el último comando de la
función. De éste comando se dedujo que la poscondición de la función es prod>0.
3. Continuando con el siguiente comando (while), se debe encontrar una invariante de
ciclo apropiada. Siguiendo una de las heurísticas más sencillas, se propone como
invariante de ciclo la poscondición prod>0 quedando así el cálculo de wp para el
while:
{prod>0 ∧ i>0}
prod*=i; {prod>0}
El cálculo anterior considera el comando i++; y la precondición se obtiene utilizando
la regla del while con correctitud parcial de la lógica de Hoare.
Para comprobar lo anterior se debe mostrar que (condición de verificación):
{prod>0 ∧ i<=n} → { prod>0 ∧ i>0}
Lo cual resulta evidente ya que si i<=n entonces se cumple que i>0 ya que n es un
número natural.
4. Continuando con el comando siguiente al while, se calcula la precondición de la
asignación i=2:
{prod>0 ∧ 2>0} i=2; { prod>0 ∧ i>0}
5. Se calcula la precondición más débil para el comando if, para comprobar el resultado
se debe demostrar:
{prod>0 ∧ n>1} → { prod>0}
Se observa claramente que a partir de la primera proposición se deduce sin problemas
la segunda.
6. El último cálculo corresponde a una asignación que queda como:
{1>0} prod=1; {prod>0}
7. Como se tiene la proposición 1>0 como precondición de la función entonces se
deduce que la precondición es T (cualquier valor). La función acepta cualquier número
natural n sin más condiciones.
Las especificaciones extraídas fueron comprobadas con la generación de condiciones de
verificación, por lo tanto son correctas parcialmente. El código anotado queda de la siguiente
manera
39
CAPÍTULO 5
PRUEBAS
int factorial (int n)
//requires T
//ensures return>0
{
int i;
int prod=1;
if(n>1)
for(i=2;i<=n;i++)
prod*=i;
return(prod);
}
Los elementos del contrato extraídos de la función factorial se resumen en la tabla 4.
Tabla 4. Especificación formal de la función factorial
Precondición
Poscondición
//requires T
//ensures return>0
5.3. Caso de prueba 3
El siguiente código [1] multiplica un número cierto número de veces:
int multiplic (int num, int cont)
{
int result;
result++;
while((num>1) && (cont<num))
{
result*=cont;
cont++;
}
return(result);
}
Como no contiene documentación alguna se analiza el código fuente (Paso 4) y se
propone como posible poscondición result>0. Con esta especificación se aplica el método de
la precondición más débil a todos los comandos, para encontrar una precondición para la
función multiplic
1. Se toma la poscondición anterior como invariante de ciclo (siguiendo la heurística más
simple) y se calcula la precondición de los dos comandos de asignación que contiene
el while:
{result>0} cont++; {result>0}
{ cont>0} result*= cont; {result>0}
2. Se aplica la regla de inferencia de composición a los comandos anteriores:
{result>0 ∧ cont>0 } result*= cont; cont++; {result>0}
3. Para demostrar la validez de lo anterior se genera la condición de verificación
utilizando la regla del comando while para la correctitud parcial de la lógica de Hoare
40
CAPÍTULO 5
PRUEBAS
(recuérdese que en la premisa incluye a la precondición y a la condición de ejecución
del while):
{(result>0 ∧ num>1) ∧ (cont<num) } → {result>0 ∧ cont>0}
Lo anterior se demuestra de la siguiente manera:
1. result>0 ∧ num>1 ∧ cont<num
2. result>0
3. num>1 ∧ cont<num
4. cont<1
5. cont>0
6. result>0 ∧ cont>0
Premisa
Eliminación de ∧ de 1
Eliminación de ∧ de 1
De 3
De 4 ya previamente se dedujo
como precondición en una
asignación
Conjunción de 2 y 5
4. Con el resultado anterior como precondición, se asciende en el código fuente para
calcular la precondición general de la función y se tiene:
{result>0 ∧ cont>0} result++; { result>0 ∧ num>1}
5. Como la precondición obtenida no condiciona a ninguno de los parámetros de entrada
a cumplir con alguna restricción, la precondición es T (verdadero ó válido para
cualquier valor entero n).
Con la generación de condiciones de verificación las especificaciones deducidas fueron
demostradas formalmente, las especificaciones deducidas son parcialmente correctas.
A continuación se presenta el código anotado:
int multiplic (int num, int cont)
//requires T
//ensures result>0
{
int result;
result++;
while((num>1) && (cont<num))
{
result*=cont;
cont++;
}
return(result);
}
La tabla 5 resume la especificación formal extraída a partir del código fuente de la
función multiplic.
Tabla 5. Especificación formal de la función multiplic
Precondición
Poscondición
//requires T
//ensures result>0
41
CAPÍTULO 5
PRUEBAS
5.4. Caso de prueba 4
El siguiente código es una implementación de la estructura de datos “pila” y cuenta con
documentación en Javadoc:
package jb;
import java.util.NoSuchElementException;
import java.util.ArrayList;
/**
* The Stack class represents a last-in-first-out stack of objects.
* @author Joseph Bergin
* @version 1.0, May 2000
* Note that this version is not thread safe.
*/
public class Stack
{
/**
* Pushes an item on to the top of this stack.
* @param item the item to be pushed.
*/
public void push(Object item){this.elements.add(item);}
/**
* Removes the object at the top of this stack and returns that object.
* @return The object at the top of this stack.
* @exception NoSuchElementException if this stack is empty.
*/
public Object pop() throws NoSuchElementException
{
int length = this.elements.size();
if (length == 0) throw new NoSuchElementException();
return this.elements.remove(length - 1);
}
/**
* Returns the object at the top of this stack without removing it.
* @return the object at the top of this stack.
* @exception NoSuchElementException if this stack is empty.
*/
public Object peek() throws NoSuchElementException
{
int length = this.elements.size();
if (length == 0) throw new NoSuchElementException();
return this.elements.get(length - 1);
}
/**
* Tests if this stack is empty.
* @return true if this stack is empty and false otherwise.
*/
public boolean isEmpty()
{
return this.elements.isEmpty();
}
private ArrayList elements = new ArrayList();
}
El primer paso es evaluar la documentación existente (Paso 1). En este caso se
considera que la documentación es fuente suficiente para extraer elementos de su
especificación. Se analiza cada uno de los métodos individualmente y se deducen las
42
CAPÍTULO 5
PRUEBAS
precondiciones y poscondiciones (Paso 3). El análisis realizado utilizando el enfoque de [16]
a cada uno de los métodos del código comentado es el siguiente:
1. Método push(). La etiqueta @param no dice nada explícito sobre alguna precondición
y al ser de tipo void, no se puede extraer una poscondición de la sentencia return.
2. Método pop(). De @return y de la sentencia return se deduce que la poscondición es:
tamaño actual de la pila = tamaño anterior de la pila − 1. Es decir, el tamaño de la
pila después de la ejecución del método pop() es el tamaño que tenía previamente
menos un elemento. La etiqueta @exception tiene implícita la precondición: la pila es
no vacía, por lo que tamaño de la pila ≥ 0.
3. Método peek(). Se analiza la sentencia return y ésta se toma como poscondición. Este
método hace una llamada a un método de la librería ArrayList y no se tienen
referencias sobre sus precondiciones, poscondiciones e invariantes. La etiqueta
@exception tiene implícita la precondición: la pila es no vacía, tamaño actual de la
pila = tamaño anterior de la pila y tamaño de la pila ≥ 0.
4. Método isEmpty(). No se puede extraer algún tipo de información relevante, ya que
hace llamada a un método de la Liberia ArrayList y no contiene etiquetas que apunten
a una posible precondición.
No se pueden extraer invariantes de clase porque no hay etiquetas en el constructor. Como
se mencionó en el análisis de las precondiciones y poscondiciones, es necesario contar con
librerías propias de Java etiquetadas mediante el mismo método. De ese modo se podría
extraer información relevante de las llamadas a métodos de librerías. Por lo que es evidente
que la información recabada no es completa ya que no se tienen precondiciones y
poscondiciones para cada uno de los métodos, ni invariantes de clase. Por lo que es necesario
realizar otro tipo de análisis para completar las especificaciones formales correspondientes a
este código fuente, ya que no se pueden inferir con la lógica de Hoare por no contar con las
estructuras contempladas (tales como if, while, entre otras) en ésta. La tabla 6 resume los
elementos extraídos del contrato correspondiente a la clase Stack.
Tabla 6. Especificación formal de la clase Stack.
Método
Precondición
push
Elemento no
encontrado
pop
tamaño de la pila ≥ 0
peek
isEmpty
tamaño actual de la
pila = tamaño anterior
de la pila y tamaño de
la pila ≥ 0
Elemento no
encontrado
Poscondición
Elemento no
encontrado
tamaño actual de la
pila = tamaño anterior
de la pila − 1
tamaño actual de la
pila = tamaño anterior
de la pila − 1
Invariante de clase
No existe información
referente a invariante
de clase en la
documentación
Elemento no
encontrado
43
CAPÍTULO 6
CONCLUSIONES
Capítulo 6) CONCLUSIONES
En este capítulo se describen las conclusiones generadas a partir del trabajo de tesis y algunas
ideas que pueden ser consideradas para futuras investigaciones.
6.1. Conclusiones
La especificación formal de la funcionalidad de código fuente no es común en la práctica.
Requiere una considerable inversión de tiempo y esfuerzo por parte del programador de la
aplicación. La especificación formal de la funcionalidad ayuda en la prevención y detección
de errores en el desarrollo de software. El objetivo de este trabajo fue “determinar la
factibilidad de extraer automáticamente la especificación formal de la funcionalidad de
componentes de reuso o servicios a partir de código fuente, para evitar que el desarrollador
invierta una considerable cantidad de tiempo y esfuerzo en la especificación manual de
precondiciones, poscondiciones e invariantes de clase”.
Para encontrar solución al problema planteado en el objetivo, fue necesario realizar un
análisis de: etapas, herramientas y técnicas que ayudan en el proceso de extraer
especificaciones formales a partir de código orientado a objetos. A partir de este análisis se
propuso un algoritmo de decisión generalizado y se formalizó con la herramienta Spin.
En el análisis del problema se determinó que las técnicas que ayudan en el proceso
son: análisis de documentación; análisis de código fuente; lógica de Hoare; cálculo de la
precondición más débil wp; análisis dinámico; y generación automática de invariantes.
De los anteriores solo análisis dinámico y generación dinámica de invariantes son
completamente automatizables. Las demás técnicas son parcialmente automatizables.
Se identificaron tres tareas para la extracción de especificaciones a partir de código
fuente: analizar documentación y código fuente; deducir especificaciones formales; y verificar
correctitud de las especificaciones deducidas.
44
CAPÍTULO 6
CONCLUSIONES
El algoritmo incluye las herramientas y técnicas listadas anteriormente. Éste presenta
dos limitaciones principales: no es completamente automatizable debido a la naturaleza
indecidible del problema; y es imprescindible el análisis humano con conocimientos
especializados, debido a que se requieren decisiones subjetivas en el proceso.
Las especificaciones deducidas son parcialmente correctas por definición al utilizar la
lógica de Hoare y el cálculo de la precondición más débil wp. Además se demostró
automáticamente que el algoritmo propuesto siempre llega a un estado de éxito o no éxito.
Al analizar el funcionamiento del algoritmo propuesto con los casos de prueba
realizados se encontró que es necesario contar con librerías propias del lenguaje de
programación con el que se encuentra implementado el código fuente previamente anotadas.
Es imprescindible el paso anterior ya que es necesario conocer las especificaciones formales
de estas librerías, para deducir precondiciones y poscondiciones a partir del código fuente.
Una vez que el contrato ha sido generado (especificaciones formales) es necesario refinarlo
con análisis humano para remover elementos que pudieran ser redundantes.
La extracción de especificaciones formales es una tarea tediosa y en algunas ocasiones
casi imposible de realizar debido a razones teóricas y prácticas. Los contratos deberían ser
anotados a priori en el proceso de desarrollo de software y así se evitaría el tener que
extraerlos a posteriori.
6.2. Aportaciones
La aportación principal de este trabajo de tesis es el algoritmo de decisión generalizado, que
como se demostró en las pruebas realizadas ayuda en el proceso de la extracción de
especificaciones formales a partir de código fuente.
Entre otras aportaciones se encuentran:
Análisis de herramientas y técnicas existentes que ayudan en el proceso.
Análisis de las tareas que son realizadas durante el proceso.
Ejemplificación de la formalización de un proceso con una herramienta automática.
El material obtenido de esta investigación permitió la redacción de un artículo para un
congreso internacional y una exposición en un congreso nacional.
 “Process for contract extraction”. International Conference on Software
Engineering Advances, 2008. ICSEA, Sliema, Malta 2008.
 “Extracción de especificaciones formales”. XLI Congreso Nacional de la
Sociedad Matemática Mexicana, Valle de Bravo, Estado de México 2008.
6.3. Trabajo futuro
Para facilitar la semiautomatización del algoritmo, se propone implementar las técnicas
existentes para la extracción de especificaciones formales que pueden ser automatizadas:
generación automática de invariantes y generación de condiciones de verificación. Además se
propone la integración de la generación de condiciones de verificación con algún demostrador
automático de teoremas que cuente con soporte directo para probar que sean correctas.
También se propone analizar los elementos faltantes para aplicar el algoritmo en el
análisis de código fuente para la búsqueda y selección de componentes y servicios web.
Además, sería necesario proponer un lenguaje de especificación para expresar contratos de
componentes y servicios web.
45
REFERENCIAS
REFERENCIAS
[1] Zamudio López , Sheydi Anel. “Identificación de funciones recurrentes en software
legado”. Tesis Maestría, Departamento de Ciencias Computacionales, Centro Nacional de
Investigación y Desarrollo Tecnológico. Diciembre 2001.
[2] Floyd, R.W. “Assigning Meanings to Programs”, Pmt. Am. Math. Sot. Symp. in Applied
Math., Vol. 19, J.T. Schwartz, ed.. American Mathematical Society, Providence, RI., 1967,
pp. 19-31.
[3] Nimmer , Jeremy W. y Ernst, Michael D. “Automatic generation of program
specifications”, In ISSTA 2002, Proceedings of the 2002 International Symposium on
Software Testing and Analysis, (Rome, Italy), July 22-24, 2002, pp. 232-242.
[4] Hoare, C.A.R., “An axiomatic basis for computer programming”, Communications of the
ACM, 12, pp. 576-583, Oct. 1969.
[5] Dijkstra, E.W., A Discipline of Programming, Prentice Hall, Englewood Cliffs, N.J., 1976.
[6] Gannod, G. C. y Cheng, B. H. C. “Strongest postcondition semantics as the formal basis
for reverse engineering”. Proceedings of the Second Working Conference on Reverse
Engineering, pag. 166, 1995.
[7] King, James C. “Symbolic execution and program testing". Comm. ACM, pags. 385-394,
1976.
[8] Meyer, Bertrand, “Applying Design by Contract". Computer, pags. 40-51 ,1992.
[9] http://www.eiffel.com . Consultada el 03 de noviembre 2007.
[10] Leavens, Gary T. y Cheon , Yoonsik. “Design by Contract with JML".
http://www.cs.iastate.edu/ leavens/JML/. Consultada el 03 de noviembre 2007 .
[11] Microsoft Research “Spec #". http://research.microsoft.com/specsharp/ . Consultada el 05
de noviembre de 2007.
[12] Mitchell, Richard y McKim, Jim. “Design by Contract, by example". Addison-Wesley,
2002.
[13] Beugnard, A. et al. “Making components contract aware". Computer 32(7). Julio 1999.
[14] Miguel, M.A. “QoS modeling language for high quality systems”. Proceedings of the
Eighth International Workshop on Object-Oriented Real-Time Dependable Systems,
2003. (WORDS 2003). Páginas 210-216, 2003.
[15] Collet , Philippe. “Functional and Non-Functional Contracts Support for ComponentOriented Programming". Comm. ACM, 2001.
46
REFERENCIAS
[16] Milanovic, N. y Malek, M. “Extracting Functional and Nonfunctional Contracts From
Java Classes and Enterprise Java Beans" Proceedings of the Workshop on Architecting
Dependable Systems (WADS 2004), Florencia, Italia 2004.
[17] Flanagan, C. y Leino, K. R.M. et al. “Extended static checking for Java”. In Proc. Conf.
Programming Language Design and Implementation, pages 234–245, 2002.
[18] Flanagan, C. y Leino, K. R.M. “Houdini, an annotation assistant for ESC/Java”. In Proc.
Int’l Symp. Formal Methods Europe on Formal Methods for Increasing Software
Productivity, pages 500–517, 2001.
[19] Bertrand Meyer y Karine Arnout, “Uncovering hidden contracts: The .net example”.
IEEE Computer, 36, No. 11, pp 48-55, November 2003.
[20] Feldman, Yishai A. y Gendler, Leon. “Discern: “Towards the Automatic Discovery of
Software Contracts”. SEFM 2006 Fourth IEEE International Conference on
Software Engineering and Formal Methods, pp. 90 – 99, 2006.
[21] Ernst, Michael D. et al. “The Daikon system for dynamic detection of likely invariants”.
Science of Computer Programming, vol. 69, no. 1--3, Dec. 2007, pp. 35-45.
[22] Javadoc Tool Homepage. http://java.sun.com/j2se/javadoc/. Consultada el 10 de
noviembre de 2007.
[23] L. Kovács. "Aligator: A Mathematica Package for Invariant Generation". Proc. of
IJCAR 2008, LNCS 5195, pp. 275-282, 2008.
[24] Mathematica. http://www.wolfram.com/. Consultada el 04 de agosto de 2008.
[25] Holzmann, Gerard J. “The Model Checker Spin”. IEEE Trans. on Software Engineering,
Vol. 23, No. 5, May 1997, pp. 279-295
[26] Holzmann, G.J. “Design and Validation of Computer Protocols”. Englewood Cliffs, N.J.:
Prentice Hall, 1991.
[27] Martin, Robert C. “The Liskov Substitution Principle”. The C++ Report, 1996.
http://www.objectmentor.com/resources/articles/lsp.pdf. Consultada el 23 de mayo de
2008.
47
ANEXO A
Anexo A
Código del algoritmo propuesto en el lenguaje Promela
En este anexo se presenta el código del algoritmo para extraer especificaciones a partir de
código fuente propuesto en este trabajo en el lenguaje Promela, para su formalización en la
herramienta Spin. Se adjunta además el código de la propiedad que se probó de este
algoritmo: ¿siempre ocurre que eventualmente el algoritmo alcanza el estado de éxito o el
estado de no éxito? Por último se presentan algunas simulaciones del algoritmo propuesto en
Spin.
El siguiente es el código del algoritmo propuesto en el lenguaje Promela.
mtype={ S_SC_Commented, S_Doc_Ana, S_SC_Ana,
S_EvalContracts, S_DeductContracts, S_PartialCorrectContracts,
S_Success,S_Failure}
mtype state;
inline setState(x) {
atomic {
state = x;
/* printf("El estado de la deduccion de contratos es: ");
printm(state);*/
printf("\n");
}
}
active proctype Contract_Extraction_Model() {
s_sc_commented:
setState(S_SC_Commented);
if
:: true -> {
printf("El codigo fuente esta documentado\n");
goto s_doc_ana; }
:: true -> {
printf("El codigo fuente no esta documentado\n");
goto s_sc_ana; }
fi;
s_doc_ana:
setState(S_Doc_Ana);
printf("Se analiza la documentacion el codigo fuente \n");
goto s_evalcontracts;
s_sc_ana:
setState(S_SC_Ana);
printf("Se analiza el codigo fuente \n");
goto s_deductcontracts;
s_evalcontracts: setState(S_EvalContracts);
if
:: true -> {
printf("El contrato deducido (a partir de documentacion y codigo fuente) es
correcto\n");
goto s_success; }
:: true -> {
printf("El contrato deducido (a partir de documentacion y codigo fuente) es
incorrecto\n");
goto s_sc_ana; }
fi;
s_deductcontracts:
setState(S_DeductContracts);
printf("Se deducen contratos mediante metodos formales (calculo de la precondicion mas debil y
logica de Hoare) \n");
goto s_partialcorrectcontracts;
48
ANEXO A
s_partialcorrectcontracts: setState(S_PartialCorrectContracts);
if
:: true -> {
printf("El contrato es parcialmente correcto\n");
goto s_success; }
:: true -> {
printf("El contrato es parcialmente incorrecto\n");
goto s_failure; }
fi;
s_success: setState(S_Success);
goto end;
s_failure: setState(S_Failure);
end:
skip;
}
El código que corresponde a la propiedad que fue demostrada del agoritmo es el
siguiente:
#define p (state==S_Success)
#define q (state==S_Failure)
never { /* [] (!p && !q) */
accept_init:
T0_init:
if
:: (! ((p)) && ! ((q))) -> goto T0_init
fi;
}
En las figuras 1, 2 y 3 se ilustra la simulación del algoritmo en Spin. En la figura 4 se
observa la representación interna (autómata de estados finitos) que Spin realiza del modelo
que toma como entrada.
49
ANEXO A
Figura 1. Simulación del algoritmo propuesto en Spin (parte 1).
Figura 2. Simulación del algoritmo propuesto en Spin (parte 2).
50
ANEXO A
Figura 3. Simulación del algoritmo propuesto en Spin (parte 3).
51
ANEXO A
Figura 4. Representación interna del modelo del algoritmo propuesto en Spin.
52
Descargar