GaloisInc / saw-script

The SAW scripting language.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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_printing 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:

  1. 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
    
  2. Things get a bit weird if you look at the post-typechecked AST for A::bar (with debug=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 the Zero a constraint, and another for the constraint arising in each guard (n == 1 or n != 1). If you look at the definitions of A::bar(n == 1) and A::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.

  3. 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 (EProofApps) equal to the number of constraints in the guard. But this isn't enough, because the type signature of the overall propguards 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.

  4. Later, when SAW imports the propguards expression into Cryptol, things go horribly awry because the subexpression A::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 is params, 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.