panic, called at src/Cryptol/TypeCheck/TypeOf.hs:65:24 in cryptol-3.1.0-inplace:Cryptol.TypeCheck.TypeOf
weaversa opened this issue · comments
This happened when SAW prove_print
ing a Cryptol property that uses numeric constraint guards.
I might need some more insight in order to reproduce this issue, as my attempts to do so have been unsuccessful thus far. I am trying something akin to:
// Test.cry
f : {n} [n]
f | n == 1 => zero
| n != 1 => zero
// test.saw
import "Test.cry";
prove_print z3 {{ f`{2} == zero }};
But that verifies without issue, so this example must be too simplistic. Are you able to submit a minimal example of the how to trigger the panic?
Alright, here is a minimal reproduction of the issue:
// A.cry
module A where
parameter
type N : #
foo : [N]
bar : {n, a} (Zero a) => [n]a
bar | n == 1 => zero
| n != 1 => zero
// B.cry
module B where
import `A as A
b : {n, a} (Zero a) => [n]a
b = A::bar`{32} { foo = zero }
// test.saw
import "B.cry";
$ ~/Software/saw-1.1/bin/saw test.saw
[14:08:54.071] Loading file "/home/ryanscott/Documents/Hacking/SAW/test.saw"
[14:08:54.098] You have encountered a bug in Cryptol's implementation.
*** Please create an issue at https://github.com/GaloisInc/cryptol/issues
%< ---------------------------------------------------
Revision: 65397a491bddc3b4e8f41053106ce8859387d662
Branch: HEAD
Location: Cryptol.TypeCheck.TypeOf.fastTypeOf
Message: unexpected polymorphic type in expression:
B::import of A at B.cry:4:1--4:15::bar(n != 1) N`1237 n`1233 a`1234 <>
with schema:
(n`1233 != 1) => {foo : [N`1237]} -> [n`1233]a`1234
CallStack (from HasCallStack):
panic, called at src/Cryptol/Utils/Panic.hs:21:9 in cryptol-3.1.0-inplace:Cryptol.Utils.Panic
panic, called at src/Cryptol/TypeCheck/TypeOf.hs:65:24 in cryptol-3.1.0-inplace:Cryptol.TypeCheck.TypeOf
%< ---------------------------------------------------
I suspect that this is more of a Cryptol issue than a SAW issue. Here is what I think is going on:
-
After Cryptol typechecks
A::bar
, we want to have something like this:A::bar : {N, n, a} (Zero a) => { foo : [N] } -> [n]a A::bar params | n == 1 => bar(n == 1)`{N, n, a} params | n != 1 => bar(n != 1)`{N, n, a} params
-
Things get a bit weird if you look at the post-typechecked AST for
A::bar
(withdebug=on
enabled):/* Not recursive */ B::import of A at B.cry:4:1--4:15::bar : {N, n, a} (Zero a) => {foo : [N]} -> [n]a B::import of A at B.cry:4:1--4:15::bar = \{N, n, a} (Zero a) (params : {foo : [N]}) -> (propguards | n == 1 => B::import of A at B.cry:4:1--4:15::bar(n == 1) N n a <> params | n != 1 => B::import of A at B.cry:4:1--4:15::bar(n != 1) N n a <> params)
Something is very odd with the guards in the
propguards
expression. I would expect each guard to have two proof applications (where each proof application is represented with a<>
): one for theZero a
constraint, and another for the constraint arising in each guard (n == 1
orn != 1
). If you look at the definitions ofA::bar(n == 1)
andA::bar(n != 1)
, you see that there are two proof arguments apiece:/* Not recursive */ B::import of A at B.cry:4:1--4:15::bar(n == 1) : {N, n, a} (Zero a, n == 1) => {foo : [N]} -> [n]a B::import of A at B.cry:4:1--4:15::bar(n == 1) = \{N, n, a} (Zero a, n == 1) (params : {foo : [N]}) -> Cryptol::zero ([n]a) <> /* Not recursive */ B::import of A at B.cry:4:1--4:15::bar(n != 1) : {N, n, a} (Zero a, n != 1) => {foo : [N]} -> [n]a B::import of A at B.cry:4:1--4:15::bar(n != 1) = \{N, n, a} (Zero a, n != 1) (params : {foo : [N]}) -> Cryptol::zero ([n]a) <>
However, the
propguards
expression only contains one proof application per guard. That seems fishy. -
Looking at the code which typechecks a numeric constraint guard in Cryptol, things get even more suspicious. To summarize what that code is doing, it will create a number of proof applications (
EProofApp
s) equal to the number of constraints in the guard. But this isn't enough, because the type signature of the overallpropguards
expression can bind additional constraints! In this example,Zero a
is one such constraint. As far as I can tell, this code doesn't take those constraints into consideration at all. -
Later, when SAW imports the
propguards
expression into Cryptol, things go horribly awry because the subexpressionA::bar(n != 1) N n a <>
has type(n != 1) => { foo : [N] } -> [n]a
, so SAW expects the next argument to be another proof application. Instead, the next argument isparams
, which triggers the panic seen above.
In light of this, a simple fix for the SAW panic is to augment the Cryptol typechecking code above like so:
diff --git a/src/Cryptol/TypeCheck/Infer.hs b/src/Cryptol/TypeCheck/Infer.hs
index 4d142697..21284d13 100644
--- a/src/Cryptol/TypeCheck/Infer.hs
+++ b/src/Cryptol/TypeCheck/Infer.hs
@@ -1266,7 +1266,7 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
P.DPropGuards cases0 -> do
asmps1 <- applySubstPreds asmps0
t1 <- applySubst t0
- cases1 <- mapM checkPropGuardCase cases0
+ cases1 <- mapM (checkPropGuardCase asmps1) cases0
exh <- checkExhaustive (P.bName b) as asmps1 (map fst cases1)
unless exh $
@@ -1401,12 +1401,12 @@ when the (automatically generated) function corresponding to the guard is
checked, assuming 'ExpandpropGuards' did its job correctly.
-}
-checkPropGuardCase :: P.PropGuardCase Name -> InferM ([Prop],Expr)
-checkPropGuardCase (P.PropGuardCase guards e0) =
+checkPropGuardCase :: [Prop] -> P.PropGuardCase Name -> InferM ([Prop],Expr)
+checkPropGuardCase asmps (P.PropGuardCase guards e0) =
do ps <- checkPropGuards guards
tys <- mapM (`checkType` Nothing) ts
let rhsTs = foldl ETApp (getV eV) tys
- rhsPs = foldl (\e _p -> EProofApp e) rhsTs ps
+ rhsPs = foldl (\e _p -> EProofApp e) rhsTs (asmps ++ ps)
rhs = foldl EApp rhsPs (map getV es)
pure (ps,rhs)
I've submitted a Cryptol-side fix in GaloisInc/cryptol#1648. Once that lands, the last remaining step would be to bump SAW's cryptol
submodule and add a SAW-specific regression test.