changeset 2064:3dd041b00087

Extend ScriptCheck to take RPCs into account
author Adam Chlipala <adam@chlipala.net>
date Sun, 24 Aug 2014 11:43:49 -0400
parents 83bdb52962c9
children 981836fa51db
files src/scriptcheck.sml tests/DynChannel.ur tests/DynChannel.urp tests/rpchan.ur tests/rpchan.urs
diffstat 5 files changed, 142 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/src/scriptcheck.sml	Sat Aug 23 11:59:34 2014 +0000
+++ b/src/scriptcheck.sml	Sun Aug 24 11:43:49 2014 -0400
@@ -1,4 +1,4 @@
-(* Copyright (c) 2009, Adam Chlipala
+(* Copyright (c) 2009, 2014, Adam Chlipala
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,6 +29,10 @@
 
 open Mono
 
+structure SM = BinaryMapFn(struct
+                           type ord_key = string
+                           val compare = String.compare
+                           end)
 structure SS = BinarySetFn(struct
                            type ord_key = string
                            val compare = String.compare
@@ -39,37 +43,108 @@
                             ["new_channel",
                              "self"])
 
+datatype rpcmap =
+         Rpc of int (* ID of function definition *)
+       | Module of rpcmap SM.map
+
+fun lookup (r : rpcmap, k : string) =
+    let
+        fun lookup' (r, ks) =
+            case r of
+                Rpc x => SOME x
+              | Module m =>
+                case ks of
+                    [] => NONE
+                  | k :: ks' =>
+                    case SM.find (m, k) of
+                        NONE => NONE
+                      | SOME r' => lookup' (r', ks')
+    in
+        lookup' (r, String.tokens (fn ch => ch = #"/") k)
+    end
+
+fun insert (r : rpcmap, k : string, v) =
+    let
+        fun insert' (r, ks) =
+            case r of
+                Rpc _ => Rpc v
+              | Module m =>
+                case ks of
+                    [] => Rpc v
+                  | k :: ks' =>
+                    let
+                        val r' = case SM.find (m, k) of
+                                     NONE => Module SM.empty
+                                   | SOME r' => r'
+                    in
+                        Module (SM.insert (m, k, insert' (r', ks')))
+                    end
+    in
+        insert' (r, String.tokens (fn ch => ch = #"/") k)
+    end
+
+fun dump (r : rpcmap) =
+    case r of
+        Rpc _ => print "ROOT\n"
+      | Module m => (print "<Module>\n";
+                     SM.appi (fn (k, r') => (print (k ^ ":\n");
+                                             dump r')) m;
+                     print "</Module>\n")
+
 fun classify (ds, ps) =
     let
         val proto = Settings.currentProtocol ()
 
         fun inString {needle, haystack} = String.isSubstring needle haystack
 
-        fun hasClient {basis, funcs, push} =
+        fun hasClient {basis, rpcs, funcs, push} =
             MonoUtil.Exp.exists {typ = fn _ => false,
                                  exp = fn ERecv _ => push
                                         | EFfiApp ("Basis", x, _) => SS.member (basis, x) 
                                         | EJavaScript _ => not push
                                         | ENamed n => IS.member (funcs, n)
+                                        | EServerCall (e, _, _, _) =>
+                                          let
+                                              fun head (e : exp) =
+                                                  case #1 e of
+                                                      EStrcat (e1, _) => head e1
+                                                    | EPrim (Prim.String (_, s)) => SOME s
+                                                    | _ => NONE
+                                          in
+                                              case head e of
+                                                  NONE => true
+                                                | SOME fcall =>
+                                                  case lookup (rpcs, fcall) of
+                                                      NONE => true
+                                                    | SOME n => IS.member (funcs, n)
+                                          end
                                         | _ => false}
 
+        fun decl ((d, _), rpcs) =
+            case d of
+                DExport (Mono.Rpc _, fcall, n, _, _, _) =>
+                insert (rpcs, fcall, n)
+              | _ => rpcs
+
+        val rpcs = foldl decl (Module SM.empty) ds
+
         fun decl ((d, _), (pull_ids, push_ids)) =
             let
-                val hasClientPull = hasClient {basis = SS.empty, funcs = pull_ids, push = false}
-                val hasClientPush = hasClient {basis = pushBasis, funcs = push_ids, push = true}
+                val hasClientPull = hasClient {basis = SS.empty, rpcs = rpcs, funcs = pull_ids, push = false}
+                val hasClientPush = hasClient {basis = pushBasis, rpcs = rpcs, funcs = push_ids, push = true}
             in
                 case d of
                     DVal (_, n, _, e, _) => (if hasClientPull e then
-                                             IS.add (pull_ids, n)
-                                          else
-                                              pull_ids,
-                                          if hasClientPush e then
-                                              IS.add (push_ids, n)
-                                          else
-                                              push_ids)
+                                                 IS.add (pull_ids, n)
+                                             else
+                                                 pull_ids,
+                                             if hasClientPush e then
+                                                 IS.add (push_ids, n)
+                                             else
+                                                 push_ids)
                   | DValRec xes => (if List.exists (fn (_, _, _, e, _) => hasClientPull e) xes then
-                                       foldl (fn ((_, n, _, _, _), pull_ids) => IS.add (pull_ids, n))
-                                             pull_ids xes
+                                        foldl (fn ((_, n, _, _, _), pull_ids) => IS.add (pull_ids, n))
+                                              pull_ids xes
                                     else
                                         pull_ids,
                                     if List.exists (fn (_, _, _, e, _) => hasClientPush e) xes then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/DynChannel.ur	Sun Aug 24 11:43:49 2014 -0400
@@ -0,0 +1,29 @@
+table channels : {Id : int, Channel:channel xbody}
+
+fun dosend (s:string) : transaction unit =
+  c <- oneRow1 (SELECT * FROM channels);
+  debug ("Sending " ^ s ^ " through the channel...");
+  send c.Channel <xml>{[s]}</xml>
+
+fun mkchannel {} : transaction xbody =
+  c <- channel;
+  s <- source <xml/>;
+  dml( DELETE FROM channels WHERE Id >= 0);
+  dml( INSERT INTO channels(Id, Channel) VALUES(0, {[c]}) );
+  return <xml>
+    <button value="Send" onclick={fn _ => rpc(dosend "blabla")}/>
+    <active code={spawn(x <- recv c; alert ("Got something from the channel"); set s x); return <xml/>}/>
+    <dyn signal={signal s}/>
+  </xml>
+
+fun main {} : transaction page =
+  s <- source <xml/>;
+  return <xml>
+    <head/>
+    <body>
+      <button value="Register" onclick={fn _ =>
+        x <- rpc(mkchannel {}); set s x
+      }/>
+      <dyn signal={signal s}/>
+    </body>
+  </xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/DynChannel.urp	Sun Aug 24 11:43:49 2014 -0400
@@ -0,0 +1,6 @@
+database dbname=DynChannel.db
+sql DynChannel.sql
+debug
+
+$/list
+DynChannel
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rpchan.ur	Sun Aug 24 11:43:49 2014 -0400
@@ -0,0 +1,18 @@
+fun remote () =
+    ch <- channel;
+    send ch "Hello World!";
+    return ch
+
+fun remoter () =
+    ch <- channel;
+    send ch "Hello World!";
+    return <xml><active code={spawn (s <- recv ch; alert s); return <xml/>}/></xml>
+
+fun main () =
+    x <- source <xml/>;
+    return <xml><body>
+      <button onclick={fn _ => ch <- rpc (remote ()); s <- recv ch; alert s}>TEST</button>
+      <button onclick={fn _ => y <- rpc (remoter ()); set x y}>TESTER</button>
+      <hr/>
+      <dyn signal={signal x}/>
+    </body></xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rpchan.urs	Sun Aug 24 11:43:49 2014 -0400
@@ -0,0 +1,1 @@
+val main : unit -> transaction page