Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Diseño por Contratos Formal y Verificación

Lógica de primer orden, tripletas de Hoare, invariantes de TADs y cálculo de wp en C

Universidad Nacional de Río Negro

3. Contratos de Estructuras de Datos e Invariantes de Clase

Introducción

Desarrollo

Fundamentos Matemáticos del Diseño por Contratos

Para sistemas de software de alta integridad o complejidad (como al implementar las estructuras dinámicas internas de un Tipo de Dato Abstracto), las especificaciones en lenguaje natural o aserciones en tiempo de ejecución resultan insuficientes. Se requiere un marco formal sustentado en la lógica matemática para probar de forma rigurosa la correctitud y coherencia de las rutinas de software.


1. Fundamentos de Lógica de Primer Orden

La Lógica de Primer Orden (First-Order Logic, FOL) extiende la lógica proposicional clásica introduciendo cuantificadores sobre elementos individuales del dominio de discurso.

Sintaxis y Alfabeto de la LPO

Un lenguaje en Lógica de Primer Orden consta de:

Variables Libres y Ligadas

Una ocurrencia de una variable en una fórmula está ligada si se encuentra dentro del alcance directo de un cuantificador (x\forall x o x\exists x). De lo contrario, se considera una variable libre. Una fórmula que carece de variables libres se denomina sentencia.

La operación de sustitución ϕ[t/x]\phi[t/x] representa el reemplazo de todas las ocurrencias libres de la variable xx en la fórmula ϕ\phi por el término tt, previniendo la colisión de identificadores.


2. Lógica de Hoare y Cálculo de Precondición Más Débil (wpwp)

La Lógica de Hoare provee un sistema axiomático formal para razonar sobre la corrección de algoritmos imperativos mediante el uso de Tripletas de Hoare:

{P} S {Q}\{P\}\ S\ \{Q\}

Donde:

Regla de la Asignación

La regla de asignación calcula analíticamente la precondición mínima requerida para asegurar una postcondición QQ tras asignar una expresión EE a la variable xx:

{Q[E/x]} x:=E {Q}\frac{}{\{Q[E/x]\}\ x := E\ \{Q\}}
Precondición Más Débil (Weakest Precondition - wpwp)

La precondición más débil wp(S,Q)wp(S, Q) describe la condición más general (menos restrictiva) sobre el estado inicial del sistema que garantiza que la ejecución de SS finalice en un estado que satisfaga la postcondición QQ.

wp(x := E,Q)Q[E/x]wp(\texttt{x := E}, Q) \equiv Q[E/x]

3. Contratos de Estructuras de Datos e Invariantes de Clase

En el diseño de Tipos Datos Abstractos (TADs), un invariante de clase o de estructura es una propiedad lógica fundamental que describe la validez interna de la representación física de los datos. Debe ser verdadera tras la construcción del objeto y preservarse antes y después de cada llamada a métodos o funciones públicas del TAD.

1
2
3
4
5
6
7
8
typedef struct {
    int *elementos;
    int tope;
    int capacidad;
} pila_t;

// Invariante de estructura Pila:
// elementos != NULL ∧ capacidad > 0 ∧ 0 <= tope <= capacidad
ACSL (ANSI/ISO C Specification Language)

ACSL permite escribir anotaciones formales directamente en los comentarios del código de C para su verificación estática mediante analizadores como Frama-C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*@ predicate pila_valida(pila_t *p) =
        \valid(p) &&
        p->elementos != NULL &&
        p->capacidad > 0 &&
        0 <= p->tope <= p->capacidad &&
        \valid(p->elementos + (0..p->capacidad-1));
  */

/*@ requires pila_valida(p);
    requires p->tope < p->capacidad;
    assigns p->tope, p->elementos[p->tope];
    ensures pila_valida(p);
    ensures p->tope == \old(p->tope) + 1;
    ensures p->elementos[p->tope - 1] == dato;
  */
void push(pila_t *p, int dato);
El Frame Problem y la directiva assigns

El Frame Problem consiste en la dificultad de especificar formalmente qué partes del estado del sistema no cambian durante la ejecución de una función. Sin una solución, las especificaciones deberían listar exhaustivamente cada variable del programa que permanece igual.

En ACSL, esto se resuelve mediante la directiva assigns, la cual especifica con exactitud las únicas variables o posiciones de memoria que la función tiene permitido modificar. El analizador formal asume de manera automática que todo lo que no figure en dicha cláusula permanece inalterado.

Ejercicios de Autoevaluación

Solution to Exercise 1

La fórmula en LPO utilizando cuantificación universal es:

i. (0ilimit<n)(arr[i]>K)\forall i.\ (0 \leq i \leq \text{limit} < n) \rightarrow (\text{arr}[i] > K)
Solution to Exercise 2
  • Ligadas: Las variables xx y zz son variables ligadas debido a que sus ocurrencias se encuentran bajo el alcance directo de los cuantificadores universal x\forall x y existencial z\exists z respectivamente.

  • Libres: La variable yy es libre porque no se encuentra bajo el alcance de ningún cuantificador lógico en la fórmula.

Solution to Exercise 3

La sustitución reemplaza la ocurrencia libre de xx por el término sin alterar la estructura ligada de yy:

(y. xy>10)[w+2/x]y. (w+2)y>10(\exists y.\ x * y > 10)[w+2/x] \equiv \exists y.\ (w + 2) * y > 10
Solution to Exercise 4

Aplicamos la regla de asignación de Hoare de atrás hacia adelante:

  1. Para la última instrucción y:=ty := t con postcondición Q=(x=By=A)Q = (x = B \land y = A):

    Q[t/y](x=Bt=A)Q[t/y] \equiv (x = B \land t = A)
  2. Para la instrucción anterior x:=yx := y sobre la condición anterior:

    (x=Bt=A)[y/x](y=Bt=A)(x = B \land t = A)[y/x] \equiv (y = B \land t = A)
  3. Para la primera instrucción t:=xt := x:

    (y=Bt=A)[x/t](y=Bx=A)(y = B \land t = A)[x/t] \equiv (y = B \land x = A)

La precondición obtenida de forma analítica es exactamente {y=Bx=A}\{y = B \land x = A\}, lo cual demuestra la corrección de la terna.

Solution to Exercise 5

Utilizando la regla de la precondición más débil para la asignación:

wp(x := x * 2,x>10)(x2>10)(x>5)wp(\texttt{x := x * 2}, x > 10) \equiv (x * 2 > 10) \equiv (x > 5)
Solution to Exercise 6

La Regla de Consecuencia permite adaptar tripletas de Hoare preexistentes a contextos más estrictos o específicos.

PP{P} S {Q}QQ{P} S {Q}\frac{P' \rightarrow P \quad \{P\}\ S\ \{Q\} \quad Q \rightarrow Q'}{\{P'\}\ S\ \{Q'\}}
  • Debilitar la precondición (PPP' \rightarrow P): Reemplazar la precondición PP por una condición PP' que es más restrictiva. Si el programa funciona asumiendo una precondición débil, seguirá siendo correcto con una más fuerte.

  • Fortalecer la postcondición (QQQ \rightarrow Q'): Reemplazar la postcondición QQ por una condición QQ' que sea menos restrictiva, garantizando más de lo inicialmente especificado.

Solution to Exercise 7

El Frame Problem es la dificultad de expresar formalmente qué variables y recursos del sistema no se modifican tras la ejecución de una rutina sin tener que listar explícitamente el infinito número de variables inalteradas.

La directiva assigns soluciona esto en ACSL permitiendo declarar un marco delimitado de variables de escritura. El analizador estático (como Frama-C) asume automáticamente que cualquier variable o celda del heap que no esté listada de forma explícita en la directiva assigns permanece inalterada, lo que simplifica drásticamente el cálculo de las condiciones de verificación.

Solution to Exercise 8
/*@ requires \valid(ptr);
    assigns *ptr;
    ensures *ptr == 0;
 */
void resetear(int *ptr);
Solution to Exercise 9

En un Rectangulo mutable, la operación set_ancho(w) tiene la postcondición de que el ancho se actualiza a w y el alto permanece inalterado.

Si Cuadrado es un subtipo, para mantener su invariante de estructura (ancho == alto), la operación set_ancho(w) debe forzosamente alterar el alto también. Esto viola la postcondición heredada de Rectangulo (que garantiza que el alto no se modifica), rompiendo el Principio de Sustitución de Liskov ya que un código cliente diseñado para Rectangulo fallaría si se le pasa un objeto de tipo Cuadrado.

Glosario

Precondición
Condición que debe cumplirse antes de invocar una función.
Postcondición
Garantía que ofrece una función al finalizar si se cumplieron sus precondiciones.
Invariante
Propiedad que debe permanecer verdadera durante el ciclo de vida de un objeto o ejecución.
Invariante de lazo (Loop Invariant)
Condición o propiedad lógica asociada a una estructura iterativa que permanece verdadera antes de ingresar al lazo, antes y después de cada vuelta, y al salir de este.

Síntesis y Resumen

En este apunte se han presentado los conceptos fundamentales del tema.

Referencias y Lecturas Complementarias

References
  1. Meyer, B. (1988). Design by Contract. Advances in Object-Oriented Software Engineering.
  2. Meyer, B. (1992). Applying “Design by Contract.” Computer, 25(10), 40–51. 10.1109/2.161279
  3. Meyer, B. (1997). Object-Oriented Software Construction (2nd ed.). Prentice Hall.