motoko
motoko copied to clipboard
experiment: unsoundness in luc/stable-functions
The PR description states that a stable function signature can evolve to a supertype. I think that's true for the uses of any stable function, but not the definition itself.
Otherwise, one alias of the stable function can evolve one way (or stay the same) while the new definition evolves to a supertype that fails to satisfy the type of the alias.
I think the fix is that when comparing the types of function signatures, the new type must be subtype of the old one, so that all references to the definition are still satisfied by the definition.
Counterexample:
version0.mo
import Prim "mo:prim";
persistent actor {
persistent func f() : {a : Int; b : Int} {
{a=0; b=0};
};
stable let g = f;
assert g().a == 0;
Prim.debugPrint "version0";
}
version1.mo
import Prim "mo:prim";
persistent actor {
persistent func f() : {a: Int; b: Int} {
{a = 1; b = 1};
};
stable let g : persistent () -> {a:Int; b: Int} = Prim.trap "unreachable";
assert g().a == 1;
stable let h : Any = f; // why does upgrade fail without this reference to f?
Prim.debugPrint "version1";
}
version2.mo
import Prim "mo:prim";
persistent actor {
persistent func f() : {b: Int} {
{b=1};
};
stable let g : persistent () -> {a:Int; b: Int} = Prim.trap "unreachable";
assert g().a == 1;
stable let h : Any = f;
}
Output:
ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
debug.print: version0
ingress Completed: Reply: 0x4449444c0000
debug.print: version1
ingress Completed: Reply: 0x4449444c0000
ingress Err: IC0502: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister trappe
d: heap out of bounds.
Consider gracefully handling failures from this canister or altering the canister to
handle exceptions. See documentation: https://internetcomputer.org/docs/current/refer
ences/execution-errors#trapped