Error en Dafny “llamada puede violar la cláusula ‘modifies’ del contexto” al utilizar una invocación de método dentro de un bucle while donde el invariante “fresh” no es una opción.
A continuación se presenta una versión simplificada (y, por lo tanto, inútil) de una clase Cola
construida con listas vinculadas. Hay un método Añadir
que agrega otro nodo de lista vinculado (fresco) al final de la cola. Si escribo otro método que toma una q: Cola
como argumento, y ejecuto q.Añadir
repetidamente en un bucle while, obtengo un error de “la llamada puede violar la cláusula de modificaciones de contexto” que no puedo resolver usando una invariante fresco
, porque q
puede tener nodos ya asignados. ¿Hay alguna forma de manejar esta situación?
“`
class LL { /* nodos de lista vinculada <em>/
var elem: int;
var next: LL?; /</em> puede ser nulo */
constructor (x: int)
ensures elem == x && next == null
{
elem, next := x, null;
}
}
class Cola { // versión simplificada de colas de enteros
var hd: LL; // implementado utilizando lista vinculada
var tl: LL;
ghost var nodes: seq<ll>; // nodos de lista vinculada
predicate invar() // invariante de clase (ignora el elemento)
reads this, nodes
{
nodes != [] && nodes[0] == hd && nodes[|nodes| – 1] == tl &&
nodes[|nodes| – 1].next == null &&
(forall i: nat {:trigger nodes[i].next} ::
i < |nodes| – 1 ==> nodes[i].next == nodes[i + 1])
}
constructor () // empezamos con un nodo
ensures invar() && |nodes| == 1 && fresh(nodes)
{
var l: LL := new LL(0);
hd, tl := l, l;
nodes := [l];
}
method Añadir(d: int) // agrega otro nodo
requires invar()
ensures invar()
ensures
nodes != [] && nodes[.. |nodes| – 1] == old(nodes) &&
fresh(nodes[|nodes| – 1])
modifies this, nodes
{
var l: LL := new LL(d);
tl.next := l;
tl := l;
nodes := nodes + [l];
}
method Foo(q: Cola)
// si asumimos esto:
// requires q.nodes == []
// pero queremos que q ya pueda tener algunos nodos, a los que añadiremos
requires q.invar()
modifies q, q.nodes
{
var i: nat := 0;
while (i < 10)
invariant i <= 10
invariant q.invar()
// entonces agregar esta invariante fresca funciona:
// invariant fresh(q.nodes)
decreases 10 – i
{
q.Añadir(i); // error: "la llamada puede violar la cláusula de modificaciones de contexto"
i := i + 1;
}
}
}“`
davy.ai
El problema surge porque el método
Add
modifica la referenciathis
y el camponodes
, y no se permite modificar un campo de un objeto que pueda tener nodos asignados.Una solución potencial podría ser introducir un nuevo método en la clase
Queue
que agregue un nodo nuevo a una cola que ya tenga nodos asignados. Este método se encargaría de crear un nodo nuevo, actualizar los enlaces entre nodos y agregar el nodo nuevo a la secuencianodes
.Entonces, en el método
Foo
, podemos verificar primero si la secuencianodes
está vacía o no. Si está vacía, simplemente podemos llamar al métodoAdd
como antes. Sin embargo, si la secuencia no está vacía, podemos llamar al nuevo método que agrega un nodo nuevo a la cola.De esta manera, aseguramos que no violamos la cláusula “modifies” del contexto actual porque no estamos modificando directamente los nodos que puedan estar asignados.
Implementación de ejemplo del nuevo método:
Implementación actualizada del método
Foo
: