Introducción a la verificación formal con SPARK ── De los contratos de Ada a la demostración matemática
· Go Komura · Ada, SPARK, Verificación Formal, GNATprove, Diseño por Contrato, Alta Integridad, Lenguaje de Programación, Alta Fiabilidad
1. Introducción ── Más allá de «ejecutar y comprobar»
La forma más habitual de garantizar la calidad del software son las pruebas.
Prueba: verifica que el programa se comporte correctamente para las entradas seleccionadas
Pero las pruebas tienen límites inherentes. El número de entradas posibles es infinito, y las pruebas por sí solas no pueden demostrar la corrección para todas ellas.
Aquí es donde entra en juego la verificación formal (Formal Verification).
Verificación formal: demuestra matemáticamente que una propiedad se cumple para todas las entradas posibles
El artículo anterior, «El atractivo del lenguaje Ada», ofreció una visión general de Ada y mencionó SPARK brevemente.
Este artículo se centra específicamente en la verificación formal práctica con SPARK y aborda lo siguiente:
Qué es SPARK y su relación con Ada
Cómo escribir contratos (Pre/Post)
Cómo demostrarlos con GNATprove
Cómo diseñar invariantes de bucle
Cómo gestionar los efectos secundarios con contratos de flujo de datos (Global/Depends)
Una estrategia de adopción gradual mediante niveles de demostración (de Stone a Platinum)
Cómo integrar SPARK en proyectos reales
El objetivo es que experimente el paso de las pruebas a la demostración a través de ejemplos de código reales.
Los fragmentos de código de este artículo están disponibles, organizados por capítulo, como colección de referencia en GitHub.
ada-spark-formal-verification - komurasoft-blog-samples (GitHub)
2. Qué es SPARK ── El subconjunto de Ada como puerta de entrada al mundo de la demostración
SPARK es un subconjunto del lenguaje Ada.
Ser un subconjunto es una decisión de diseño deliberada.
Ada completo → muy expresivo, pero verificarlo por completo no es realista
SPARK → restringido a características verificables, lo que hace realista la demostración
Las principales restricciones que impone SPARK son:
Gestión dinámica de memoria mediante punteros (tipos access) → controlada por un modelo de propiedad
Uso libre de manejadores de excepciones → permitido de forma restringida
Estructuras de datos recursivas → restringidas por ser difíciles de demostrar
Uso libre de tareas (tasking) → restringido mediante el perfil Ravenscar
La palabra «restricciones» puede sonar limitante, pero estas restricciones son el precio de la demostrabilidad.
El núcleo del conjunto de herramientas de SPARK es GNATprove, proporcionado por AdaCore.
Cómo funciona GNATprove:
1. Analiza código Ada anotado con SPARK
2. Traduce los contratos (Pre/Post) y las aserciones al lenguaje intermedio Why3
3. Intenta demostraciones con probadores automáticos como Z3, CVC4 y Alt-Ergo
4. Informa los resultados como mensajes directamente sobre el código fuente
Un punto crucial: SPARK está construido sobre Ada.
Código SPARK = código Ada + anotaciones de contrato
Dicho de otro modo, SPARK es una extensión natural del desarrollo cotidiano en Ada. No hace falta aprender una sintaxis completamente nueva.
3. Instalar GNATprove y la primera demostración
Primero, preparemos un entorno para ejecutar GNATprove.
Con Alire se puede crear un proyecto listo para SPARK con el siguiente comando:
alr init --bin spark_demo
cd spark_demo
Como GNATprove viene incluido con el compilador GNAT, el comando gnatprove está disponible en cuanto el proyecto compila con alr build.
Como primer objetivo de demostración, escribamos una función que devuelva el valor absoluto.
package Simple_Proof with SPARK_Mode is
function Abs_Value (X : Integer) return Integer
with Post => Abs_Value'Result >= 0;
end Simple_Proof;
package body Simple_Proof with SPARK_Mode is
function Abs_Value (X : Integer) return Integer is
begin
if X < 0 then
return -X;
else
return X;
end if;
end Abs_Value;
end Simple_Proof;
Puntos clave a observar:
with SPARK_Mode se añade tanto a la especificación del paquete como al cuerpo
La condición Post declara la propiedad «el resultado es no negativo»
El atributo 'Result permite referirse al valor de retorno de la función
Ahora ejecutemos GNATprove sobre este código:
gnatprove -P spark_demo.gpr
El resultado será similar a este:
SUMMARY
-------
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
simple_proof.adb:5:15: info: range check proved
simple_proof.adb:6:16: info: range check proved
simple_proof.ads:3:19: info: postcondition proved
El mensaje postcondition proved significa que se ha demostrado matemáticamente que, para todas las entradas Integer posibles, el valor de retorno es no negativo.
Esta es la experiencia de entrada a SPARK.
4. Fundamentos del diseño por contrato ── Escribir Pre y Post
Las demostraciones de SPARK comienzan escribiendo contratos.
Los contratos son una característica del lenguaje Ada 2012, pero en SPARK se convierten en la entrada de la demostración.
procedure Transfer (From, To : in out Account; Amount : Positive)
with Pre => From.Balance >= Amount,
Post => From.Balance = From.Balance'Old - Amount
and then
To.Balance = To.Balance'Old + Amount;
Directrices para diseñar Pre y Post:
Pre (precondición):
Responsabilidad de quien llama
«Si me llamas cumpliendo esta condición, garantizo la Post»
Lo bastante débil para ser satisfacible, lo bastante fuerte para ser útil
Post (poscondición):
Responsabilidad de la implementación
«Después de la llamada, el mundo está en este estado»
El atributo 'Old permite referirse a los valores previos a la llamada
Demasiado fuerte hace la implementación demasiado restrictiva; demasiado débil no demuestra nada útil
Errores comunes y cómo evitarlos:
Error 1: Pre demasiado fuerte
Pre => X > 0 and X < 100 and X /= 50 and ...
→ Comprobar: ¿puede quien llama satisfacer siempre esta condición?
→ No restringir Pre solo porque las pruebas pasen
Error 2: Post demasiado débil
Post => True
→ No garantiza nada. La demostración carece de sentido.
Error 3: Olvidar los efectos secundarios en Post
Post => Result = X * 2 (se pasa por alto la actualización de una variable global)
→ Hacer explícitos los efectos secundarios con contratos Global/Depends (sección 9)
5. Demostrar la ausencia de desbordamiento ── Garantizar la seguridad numérica
Una de las cosas más valiosas en la práctica que SPARK puede demostrar es la ausencia de desbordamiento (overflow).
Considere este código:
procedure Increment (X : in out Integer)
with SPARK_Mode,
Pre => X < Integer'Last,
Post => X = X'Old + 1;
Pre => X < Integer'Last es la clave. Con este contrato, GNATprove puede demostrar que la suma no se desbordará.
Sin contrato, GNATprove advierte de un posible desbordamiento:
medium: overflow check might fail
Lo mismo se aplica a cálculos más complejos:
function Average (A, B : Integer) return Integer
with SPARK_Mode,
Pre => (if A >= 0 and B >= 0 then A <= Integer'Last - B
elsif A < 0 and B < 0 then A >= Integer'First - B),
Post => (if A <= B then A <= Average'Result and Average'Result <= B
else B <= Average'Result and Average'Result <= A);
La Pre parece compleja, pero simplemente expresa que «la suma A+B no se desborda», distinguiendo casos según el signo.
Idea clave:
La esencia de la demostración de ausencia de desbordamiento consiste
en declarar en Pre que el resultado de una suma o multiplicación cabe
dentro del rango del tipo
Puede parecer tedioso, pero una vez escrito, nunca más le atormentará
un error de desbordamiento
6. Invariantes de bucle ── Demostrar propiedades de los bucles
Una de las partes más difíciles de la verificación formal es la demostración de bucles.
Los bucles se ejecutan un número arbitrario de veces, por lo que las pruebas no pueden cubrir todos los casos. SPARK utiliza para ello los invariantes de bucle (Loop Invariants).
function Sum_Of_Naturals (N : Natural) return Natural
with SPARK_Mode,
Post => Sum_Of_Naturals'Result = (N * (N + 1)) / 2;
function Sum_Of_Naturals (N : Natural) return Natural is
Result : Natural := 0;
begin
for I in 1 .. N loop
Result := Result + I;
pragma Loop_Invariant (Result = (I * (I + 1)) / 2);
end loop;
return Result;
end Sum_Of_Naturals;
Diseñar invariantes de bucle exige el siguiente planteamiento:
1. Escribir una propiedad que se cumpla al inicio de cada iteración
2. Elegir una que, tras la última iteración, implique la Post deseada
3. El invariante expresa como fórmula el cálculo parcial realizado hasta ese punto
He aquí otro ejemplo: la búsqueda en un array.
function Find (Arr : Array_Of_Integer; Target : Integer) return Natural
with SPARK_Mode,
Post => (if Find'Result = 0 then
(for all K in Arr'Range => Arr (K) /= Target)
else
Arr (Find'Result) = Target);
function Find (Arr : Array_Of_Integer; Target : Integer) return Natural is
begin
for I in Arr'Range loop
if Arr (I) = Target then
return I;
end if;
pragma Loop_Invariant
(for all K in Arr'First .. I => Arr (K) /= Target);
end loop;
return 0;
end Find;
El invariante aquí es: «en el rango examinado hasta ahora, Target no existe».
Principios para diseñar invariantes de bucle:
Expresar como fórmula: «tras n iteraciones, ¿qué podemos afirmar?»
En los bucles sobre arrays, el patrón habitual es «para el rango ya procesado, se cumple P»
Un invariante demasiado fuerte no se puede demostrar; uno demasiado débil no implica la Post
Encontrar el equilibrio adecuado es el verdadero arte de demostrar bucles
7. Pragmas de aserción ── Propiedades parciales dentro del código
Además de los contratos (Pre/Post), SPARK permite escribir aserciones (Assert) directamente en el cuerpo del código.
procedure Divide (A, B : Integer; Q, R : out Integer)
with SPARK_Mode,
Pre => B /= 0,
Post => A = Q * B + R and R >= 0 and R < abs (B);
procedure Divide (A, B : Integer; Q, R : out Integer) is
begin
Q := A / B;
R := A rem B;
pragma Assert (A = Q * B + R);
pragma Assert (R >= 0);
pragma Assert (R < abs (B));
end Divide;
Uso de los distintos pragmas:
Pre/Post: Promesas en la entrada y salida del subprograma
Assert: Una propiedad que debe cumplirse en un punto concreto del código
Loop_Invariant: Una propiedad que se mantiene en cada iteración del bucle
Loop_Variant: Una cantidad decreciente para demostrar que el bucle termina
Buenos casos de uso para Assert:
Verificar resultados intermedios de cálculos complejos
Comprobar el estado tras una rama if/else
Confirmar propiedades de los valores de retorno tras llamar a un procedimiento
Aportar lemas que faciliten demostraciones posteriores
8. Contratos de flujo de datos ── Global y Depends
Una de las características potentes de SPARK son los contratos de flujo de datos.
Declaran explícitamente qué variables globales lee y escribe un subprograma.
package Counter_Unit with SPARK_Mode is
Count : Natural := 0;
procedure Increment
with Global => (In_Out => Count);
procedure Reset
with Global => (Output => Count);
function Get_Value return Natural
with Global => (Input => Count);
end Counter_Unit;
Los modos del contrato Global son:
Input: solo lectura (para funciones)
Output: solo escritura (para inicialización)
In_Out: lectura y escritura (para actualizaciones)
Además, las dependencias entre variables pueden expresarse con Depends:
procedure Transfer
(From, To : in out Account_Type)
with Global => (Input => Exchange_Rate),
Depends => (From =>+ (From, Exchange_Rate),
To =>+ (To, Exchange_Rate));
=>+ significa «depende del valor anterior más estas entradas adicionales».
Ventajas de los contratos de flujo de datos:
1. «¿Qué toca esta función?» se ve de un vistazo
2. Los efectos secundarios no intencionados se detectan en tiempo de compilación
3. Alimentan el análisis de flujo de información de las variables
4. Ayudan a visualizar el flujo de datos en sistemas grandes
9. Niveles de demostración ── Adopción gradual de Stone a Platinum
SPARK define el concepto de niveles de demostración (Proof Levels).
Intentar demostrar por completo todo el código de una vez suele llevar a la frustración. Por eso SPARK ofrece una estrategia para aumentar el rigor de la demostración de forma gradual.
Stone (nivel 0):
Solo verificación de la sintaxis de los contratos y la consistencia de tipos
El primer nivel que hay que alcanzar
Bronze (nivel 1):
Stone + demostración de ausencia de lecturas de variables no inicializadas
Garantía de corrección básica
Silver (nivel 2):
Bronze + demostración de ausencia de errores en tiempo de ejecución
(fuera de rango, desbordamiento, división por cero)
El objetivo más pragmático
Gold (nivel 3):
Silver + demostración de las condiciones Pre/Post
Garantía de corrección funcional completa
Platinum (nivel 4):
Gold + demostración de la no interferencia de datos y el flujo de
información
Para sistemas orientados a la seguridad
Una estrategia de adopción práctica:
Fase 1: llevar todo el proyecto al nivel Stone
→ Detectar errores al escribir los contratos
Fase 2: llevar los módulos críticos al nivel Silver
→ Eliminar desbordamientos y accesos fuera de rango
→ La mayoría de los errores reales se evitan aquí
Fase 3: demostrar la lógica central al nivel Gold
→ Garantizar matemáticamente la corrección funcional
→ Demostrar la corrección de los algoritmos
Fase 4: pasar a Platinum si hay requisitos de seguridad
→ Detectar rutas de fuga de información
10. Un flujo de trabajo práctico de demostración ── Reducir el retrabajo
Aquí tiene un flujo de trabajo para usar GNATprove en el día a día:
1. Diseñar los tipos y las especificaciones
Decidir las restricciones de rango y los invariantes de tipo
2. Escribir los contratos (Pre/Post)
Expresar la especificación como contrato antes de implementar
3. Conseguir que compile
Corregir los errores de compilación con GNAT
4. Ejecutar GNATprove en el nivel Stone
gnatprove -P proj.gpr --level=0
5. Revisar las advertencias y refinar los contratos si es necesario
Prestar especial atención a los problemas de inicialización
6. Buscar el nivel Silver
gnatprove -P proj.gpr --level=2
Eliminar todos los errores en tiempo de ejecución
7. Añadir invariantes de bucle donde sea necesario
Si una demostración falla, comprobar si faltan invariantes
8. Demostrar la corrección funcional en el nivel Gold
gnatprove -P proj.gpr --level=3
Casos comunes de «no se puede demostrar» y cómo abordarlos:
Caso 1: «medium: postcondition might fail»
→ Post demasiado fuerte, o Pre demasiado débil
→ O invariantes de bucle insuficientes
Caso 2: «medium: overflow check might fail»
→ Restringir los rangos de valores en Pre
→ O reducir el rango del propio tipo
Caso 3: «medium: array index check might fail»
→ Usar un invariante de bucle para mostrar que el rango del bucle
está dentro de los límites del array
→ Usar for I in Arr'Range para ayudar al reconocimiento automático
de rangos de SPARK
Caso 4: «prover timeout»
→ El probador ha agotado el tiempo; dividir con invariantes graduales
→ Dividir las funciones grandes en funciones más pequeñas
11. Usar SPARK y las pruebas juntos ── Entender la relación complementaria
La verificación formal y las pruebas no son adversarias; se complementan.
Demostraciones de SPARK:
Demuestran propiedades para todas las entradas posibles
Solo cubren las propiedades escritas en los contratos
Los casos no demostrables se dejan a la revisión manual o a las pruebas
Pruebas:
Verifican el comportamiento real para las entradas seleccionadas
Pueden descubrir supuestos implícitos no escritos en los contratos
Pueden verificar la interacción con el entorno de ejecución
Patrones de uso combinado:
1. Garantizar la ausencia de errores en tiempo de ejecución (nivel
Silver) con SPARK
2. Verificar la corrección concreta de entrada/salida con pruebas
unitarias
3. Demostrar la lógica central en el nivel Gold
4. Concentrar las pruebas en las partes no demostrables
Ada dispone del framework de pruebas unitarias AUnit. Una combinación práctica consiste en consolidar tipos y contratos con SPARK y luego probar el comportamiento con AUnit.
12. Leer los resultados de GNATprove ── Interpretar los mensajes
La salida de GNATprove tiene tres niveles de gravedad:
info: demostración exitosa
medium: demostración fallida (advertencia). Requiere revisión manual
high: demostración fallida (error). Probable violación del contrato
Mensajes comunes y su significado:
«postcondition proved» se ha demostrado la condición Post
«range check proved» se ha demostrado la comprobación de rango
«overflow check proved» se ha demostrado la ausencia de desbordamiento
«index check proved» se han demostrado los límites del índice del array
«divide by zero check proved» se ha demostrado la ausencia de división por cero
«might fail» no se pudo demostrar
«cannot prove» el probador no pudo completar la demostración
«prover timeout» la demostración no terminó dentro del límite de tiempo
Procedimiento a seguir ante might fail:
1. Leer el código y juzgar si realmente puede fallar
2. Si puede fallar, corregir el código
3. Si no debería fallar, reforzar el contrato (Pre / invariante)
4. Si aun así no se puede demostrar, declararlo como suposición con
pragma Assume (atención: Assume es una suposición no demostrada,
úsese con cautela)
13. Integración en proyectos reales
Un enfoque realista para introducir SPARK en un proyecto existente:
1. Empezar por el código nuevo
No intentar convertir todo el código existente a SPARK de una vez
Activar SPARK_Mode en los módulos recién escritos
2. Buscar Silver
Gold (demostración funcional completa) es lo ideal, pero se empieza
con Silver (sin errores en tiempo de ejecución)
Incluso solo evitar el desbordamiento tiene un valor práctico enorme
3. Escribir los contratos partiendo de las interfaces
Escribir los contratos en la especificación del paquete (.ads) antes
de implementar
Con una especificación sólida, cualquier implementador puede escribir
código que satisfaga los contratos
4. Integrar en CI/CD
Añadir gnatprove al pipeline de CI
Detener la compilación (o al menos advertir) si falla una demostración
5. Acumular informes de demostración
Generar informes con gnatprove --report=all
Seguir la evolución de los elementos no demostrados con el tiempo
Para proyectos con código mixto de C/C++ y Ada, un enfoque gradual resulta eficaz:
1. Escribir los módulos nuevos en Ada/SPARK
2. Interactuar con C/C++ a través de Interfaces.C
3. Migrar a SPARK las estructuras de datos críticas y la lógica de
verificación
4. Ampliar gradualmente la cobertura de SPARK
14. Límites y precauciones de SPARK
SPARK tampoco es una solución universal. Comprender honestamente sus límites conduce a una aplicación correcta.
Lo que SPARK puede demostrar:
SPARK solo demuestra las propiedades escritas en los contratos
Las propiedades omitidas (olvidadas al escribir) no se demuestran
Ejemplo: demostrar en Post que el resultado está ordenado no garantiza
que se preserven los elementos originales, a menos que eso también
se escriba
Límites de los probadores:
Algunas propiedades complejas superan lo que pueden manejar los
probadores automáticos
En esos casos hace falta demostración interactiva (Coq, etc.)
Aun así, la demostración automática basta para la mayoría de los
casos prácticos
Restricciones del lenguaje:
El código con uso intensivo de punteros no se puede convertir a SPARK
La demostración de la asignación dinámica de memoria es limitada
La demostración de estructuras de datos recursivas es difícil
Dominio por parte de los desarrolladores:
Escribir contratos requiere práctica
Diseñar invariantes de bucle lleva especialmente tiempo aprenderlo
Un plan de mejora de competencias para todo el equipo es indispensable
15. Conclusión ── Hacer de la demostración parte del desarrollo diario
Hemos repasado la práctica de la verificación formal con SPARK.
SPARK es un subconjunto de Ada, restringido a características verificables
Los contratos (Pre/Post) son la entrada de las demostraciones. Los
contratos de Ada 2012 funcionan directamente
GNATprove ejecuta demostraciones automáticas e informa los resultados
a nivel de código fuente
Los invariantes de bucle demuestran propiedades de los bucles
Global/Depends hacen explícito el flujo de datos y gestionan los
efectos secundarios
Los niveles de demostración (de Stone a Platinum) permiten aumentar
el rigor por etapas
Buscar primero el nivel Silver (sin errores en tiempo de ejecución) es
la recomendación práctica
Las pruebas y las demostraciones no son opuestas: se complementan
Puede que exista la idea preconcebida de que «la verificación formal es demasiado difícil y solo apta para proyectos especiales».
Pero la combinación actual de SPARK y GNATprove ha alcanzado el siguiente nivel:
1. Escribir un contrato. Es fundamentalmente la misma actividad de
diseño que escribir tipos o pruebas
2. Ejecutar gnatprove. Se siente como compilar
3. Mirar los resultados y corregir el código o el contrato
Este ciclo no es distinto del flujo de desarrollo diario en el que se corrige código leyendo los mensajes de error del compilador.
La diferencia es esta: mientras que un compilador garantiza que «la sintaxis es correcta», GNATprove garantiza «la corrección para todas las entradas».
El lema de Ada presentado en el artículo anterior —«los errores no se encuentran; los tipos y los contratos hacen imposible escribirlos»— avanza al siguiente nivel con SPARK:
La ausencia de errores está garantizada por la demostración.
Empiece añadiendo SPARK_Mode y un Post a una función pequeña, y luego ejecute gnatprove.
Cuando vea el mensaje verde postcondition proved, su forma de ver el aseguramiento de la calidad del software cambiará.
Referencias
- Colección de código de referencia organizada por capítulo - komurasoft-blog-samples (GitHub)
- SPARK - AdaCore
- Introduction to SPARK - learn.adacore.com
- SPARK 2014 Reference Manual
- GNATprove User’s Guide
- Ada Reference Manual (Ada 2022)
- Alire - Ada Library Repository
- Why3 - Platform for Deductive Program Verification
- El atractivo del lenguaje Ada ── Expresar el diseño mediante tipos, sostener software que funciona durante décadas - Este sitio
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Guía práctica de Process Monitor (ProcMon): identifica en 10 minutos por qué «no se lee la configuración» o aparece «ACCESS DENIED»
«Modifiqué el archivo de configuración pero no se refleja», «funcionaba hasta ayer, pero hoy no arranca»: antes de tocar el código fuente...
Cuándo no conviene migrar una aplicación de Windows a la Web: una tabla de decisión y la solución realista de la "división"
Cada vez son más las solicitudes para migrar aplicaciones Windows de uso interno a la Web, pero en aplicaciones con integración de dispos...
Internacionalización de aplicaciones WinForms/WPF: la práctica de resx, ensamblados satélite y cambio de cultura
Organizamos desde una perspectiva práctica la internacionalización de aplicaciones de escritorio Windows: la diferencia entre CurrentCult...
El CSV no es «solo texto plano»: guía práctica del manejo de CSV en aplicaciones empresariales con C# (codificación de caracteres, compatibilidad con Excel, protección contra inyección)
Repasamos los patrones típicos de incidentes en la entrada/salida de CSV en aplicaciones empresariales -el parseo casero con Split(','), ...
No envuelvas HttpClient en un using ── Comunicación HTTP en aplicaciones de negocio C# (patrones de creación, tiempos de espera y reintentos)
Crear HttpClient con using en cada solicitud agota los sockets, y convertirlo en static hace que no siga los cambios de DNS. Explicamos e...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Qué es SPARK y qué relación tiene con Ada?
- SPARK es un subconjunto de Ada deliberadamente restringido, diseñado para que las propiedades de un programa puedan demostrarse matemáticamente. Limita características difíciles de verificar —uso libre de punteros, manejo ilimitado de excepciones, estructuras de datos recursivas y tareas sin restricciones— a cambio de demostrabilidad. El código SPARK es código Ada más anotaciones de contrato, por lo que constituye una extensión natural del desarrollo cotidiano en Ada y no un lenguaje distinto; la herramienta GNATprove traduce los contratos al lenguaje intermedio Why3 e intenta demostraciones con probadores automáticos como Z3, CVC4 y Alt-Ergo.
- ¿En qué se diferencia la verificación formal con SPARK de las pruebas?
- Las pruebas verifican que el programa se comporte correctamente para las entradas seleccionadas; la verificación formal demuestra matemáticamente que una propiedad se cumple para todas las entradas posibles. Ambas se complementan en lugar de competir: SPARK solo demuestra las propiedades escritas en los contratos, mientras que las pruebas pueden descubrir supuestos implícitos nunca formalizados y verificar la interacción con el entorno de ejecución. Una combinación práctica consiste en garantizar la ausencia de errores en tiempo de ejecución con SPARK, verificar comportamientos concretos de entrada/salida con pruebas unitarias y concentrar las pruebas en las partes que no se pueden demostrar.
- ¿Cuáles son los niveles de demostración de SPARK y cuál conviene buscar primero?
- SPARK define cinco niveles de rigor creciente: Stone (sintaxis de los contratos y consistencia de tipos), Bronze (añade la demostración de ausencia de lecturas no inicializadas), Silver (añade la demostración de ausencia de errores en tiempo de ejecución como desbordamientos, violaciones de rango y división por cero), Gold (añade la demostración de corrección funcional mediante Pre/Post) y Platinum (añade la no interferencia de datos y el flujo de información). Silver es el objetivo inicial más pragmático —la mayoría de los errores reales se eliminan ya con la ausencia de errores en tiempo de ejecución— y Gold merece la pena para la lógica central cuya corrección deba garantizarse.
- ¿Qué es un invariante de bucle y por qué lo necesitan las demostraciones de SPARK?
- Un invariante de bucle es una propiedad que se cumple al inicio de cada iteración, escrita con pragma Loop_Invariant. Los bucles se ejecutan un número arbitrario de veces, por lo que el probador necesita el invariante para razonar sobre todas las iteraciones a la vez: expresa como fórmula el cálculo parcial realizado hasta ese punto, y tras la última iteración debe implicar la poscondición deseada. Diseñar invariantes es la habilidad más delicada en las demostraciones de bucles: un invariante demasiado fuerte no se puede demostrar, y uno demasiado débil no implica la poscondición.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.
Enlaces públicos