packages feed

cpsa-4.4.1: src/CPSA/LoadFormulas.hs

-- Loads formulas from S-expressions as part of the CPSA loader process.

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module CPSA.LoadFormulas
    (loadSentence,
     sortedVarsOfStrings,
     sortedVarsOfNames,
     varsInTerm,
     loadTerms,
     loadFactList, loadDisjuncts, loadConclusions, loadConclusion,
     VarListSpec, Mode(..),
     lookupRole) where

import Control.Monad

import qualified Data.List as L
import CPSA.Lib.SExpr
import CPSA.Signature
import CPSA.Algebra
import CPSA.Protocol
-- import CPSA.Characteristic

{--
import System.IO.Unsafe
z :: Show a => a -> b -> b
z x y = unsafePerformIO (print x >> return y)

zz :: Show a => a -> a
zz x = z x x
--}

-- Moved this to Algebra.hs:
-- type VarListSpec = [(String,[String])]

showst :: Term -> ShowS
showst t =
    shows $ displayTerm (addToContext emptyContext [t]) t

sortedVarsOfNames :: Sig -> Gen -> String -> [String] -> (Gen, [Term])
sortedVarsOfNames sig g sortName =
    L.foldr
    (\name (g,vs) ->
         let (g', v) = newVar sig g name sortName in
         (g', (v : vs)))
    (g, [])

sortedVarsOfStrings :: Sig -> Gen -> VarListSpec -> (Gen,[Term])
sortedVarsOfStrings sig g =
    foldr (\(s,varnames) (g,soFar) ->
               let (g', vars) = sortedVarsOfNames sig g s varnames in
               (g', vars ++ soFar))
              (g,[])

varsInTerm :: Term -> [Term]
varsInTerm t =
    foldVars (\vars v -> v : vars) [] t

{--

-- These two next procedures were unused.

varOfName :: MonadFail m => [AForm] -> String -> m Term
varOfName aforms s =
    case (foldr (\aform vars -> filter (((==) s) . varName)
                                $ aFreeVars vars aform)
          []                          -- no vars to begin with
          aforms) of
      [v] -> return v
      [] -> fail ("varOfName:  Variable of name "
                  ++ s ++ "not found in formulas "
                         ++ (show aforms))
      _ -> fail ("varOfName:  Multiple variables of name "
                 ++ s ++ "found in formulas "
                        ++ (show aforms))

-- Given a conjunction (list of AForms) and a variable specification,
-- we would like to retrieve, for each of the variable names in the
-- variable specification, a unique variable occurring in the
-- conjunction with that name.  We return them in the same order as in
-- the variable specification.

-- This procedure fails if there is no variable of a given name, or if
-- there are more than one.

varsOfVarSpecList :: MonadFail m => [AForm] -> VarListSpec -> m [Term]
varsOfVarSpecList aforms [] = return []
varsOfVarSpecList aforms ((_, names) : rest) =
    do
      varsRest <- varsOfVarSpecList aforms rest
      newVars <- (mapM (varOfName aforms) names)
      return (newVars ++ varsRest)

--}

loadTerms :: MonadFail m => Sig -> [Term] -> [SExpr Pos] -> m [Term]
loadTerms sig vars =
    mapM (loadTerm sig vars False)

loadFactList :: MonadFail m => Sig -> [Term] ->
                [SExpr Pos] -> m [(String, [Term])]
loadFactList sig vars =
    mapM (loadAFact sig vars)

loadAFact :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m (String, [Term])
loadAFact sig vars (L _ (S _ name : fs)) =
    do
      fs <- mapM (loadTerm sig vars False) fs
      return $ (name, fs)
loadAFact _ _ x = fail (shows (annotation x) "Malformed fact")

lookupRole :: MonadFail m => Pos -> Prot -> String -> m Role
lookupRole _ p role  | role == "" =
    return $ listenerRole p
lookupRole pos p role =
    case L.find (\r -> role == rname r) (roles p) of
      Nothing ->
          fail (shows pos $ "Role " ++ role ++ " not found in " ++ pname p)
      Just r -> return r

data Mode
  = RoleSpec
  | UnusedVars
  | Liberal

-- Load a single security goal, a universally quantified formula
-- Returns the goal and the antecedent with position information.

loadSentence :: MonadFail m => Sig -> Mode -> Pos -> Prot -> Gen ->
                SExpr Pos -> m (Gen, Goal, Conj)
loadSentence sig md _ prot g (L pos [S _ "forall", L _ vs, x]) =
  do
    (g, vars) <- loadVars sig g vs
    loadImplication sig md pos prot g vars x
loadSentence _ _ pos _ _ _ = fail (shows pos "Bad goal sentence:  No forall")

-- Load the top-level implication of a security goal

loadImplication :: MonadFail m => Sig -> Mode -> Pos -> Prot -> Gen ->
                   [Term] -> SExpr Pos -> m (Gen, Goal, Conj)
loadImplication sig md _ prot g vars (L pos [S _ "implies", a, c]) =
  do
    antec <- loadCheckedConj sig md pos prot vars vars a
    (g, vc) <- loadConclusion sig pos prot g vars c
    let f (evars, form) = (evars, map snd form)
    let consq = map f vc        -- Expunge position info
    let goal =
          Goal { uvars = vars,
                 antec = map snd antec,
                 consq = consq,
                 concl = map snd consq }
    return (g, goal, antec)
loadImplication _ _ pos _ _ _ _ = fail (shows pos "Bad goal implication")

-- To load a number of conclusions from an SExpr

loadConclusions :: MonadFail m => Sig -> Prot -> Gen -> [Term] ->
                  [SExpr Pos] -> m (Gen, [[([Term], Conj)]])
loadConclusions _ _ g _ [] = return (g,[])
loadConclusions sig prot g vars (x : rest) =
    do
      (g,newConcl) <- loadConclusion sig (annotation x) prot g vars x
      (g',concls) <- loadConclusions sig prot g vars rest
      return (g', newConcl : concls)

-- The conclusion must be a disjunction.  Each disjunct may introduce
-- existentially quantified variables.

loadConclusion :: MonadFail m => Sig -> Pos -> Prot -> Gen -> [Term] ->
                  SExpr Pos -> m (Gen, [([Term], Conj)])
loadConclusion _ _ _ g _ (L _ [S _ "false"]) = return (g, [])
loadConclusion sig _ prot g vars (L pos (S _ "or" : xs)) =
  loadDisjuncts sig pos prot g vars xs []
loadConclusion sig pos prot g vars x =
  do
    (g, a) <- loadExistential sig pos prot g vars x
    return (g, [a])

loadDisjuncts :: MonadFail m => Sig -> Pos -> Prot -> Gen -> [Term] ->
                 [SExpr Pos] -> [([Term], Conj)] -> m (Gen, [([Term], Conj)])
loadDisjuncts _ _ _ g _ [] rest = return (g, reverse rest)
loadDisjuncts sig pos prot g vars (x : xs) rest =
  do
    (g, a) <- loadExistential sig pos prot g vars x
    loadDisjuncts sig pos prot g vars xs (a : rest)

loadExistential :: MonadFail m => Sig -> Pos -> Prot -> Gen -> [Term] ->
                   SExpr Pos -> m (Gen, ([Term], Conj))
loadExistential sig _ prot g vars (L pos [S _ "exists", L _ vs, x]) =
  do
    (g, evars) <- loadVars sig g vs
    as <- loadCheckedConj sig -- RoleSpec
          --UnusedVars
          Liberal pos prot (evars ++ vars) evars x
    return (g, (evars, as))
loadExistential sig pos prot g vars x =
  do
    as <- loadCheckedConj sig RoleSpec pos prot vars [] x
    return (g, ([], as))

-- Load a conjunction and check the result as determined by the mode
-- md.

loadCheckedConj :: MonadFail m => Sig -> Mode -> Pos -> Prot -> [Term] ->
                   [Term] -> SExpr Pos -> m Conj
loadCheckedConj sig RoleSpec pos prot vars unbound x =
  loadRoleSpecific sig pos prot vars unbound x
loadCheckedConj sig UnusedVars pos prot vars unbound x =
  loadUsedVars sig pos prot vars unbound x
loadCheckedConj sig Liberal pos prot vars unbound x =
  loadLiberalVars sig pos prot vars unbound x

--- Load a conjunction of atomic formulas and ensure the formula is
--- role specific.

loadRoleSpecific :: MonadFail m => Sig -> Pos -> Prot -> [Term] ->
                    [Term] -> SExpr Pos -> m Conj
loadRoleSpecific sig pos prot vars unbound x =
  do
    as <- loadConjunction sig pos prot vars x
    let as' = L.sortBy (\(_, x) (_, y) -> aFormOrder x y) as
    -- Remove vars that are in facts
    let unbound' = foldl factSpecific unbound as'
    unbound <- foldM roleSpecific unbound' as'
    case unbound of
      [] -> return as'
      (v : _) -> fail (shows (annotation x) (showst v " not used"))

-- Load a conjunction of atomic formulas and ensure that all declared
-- variables are used.

loadUsedVars :: MonadFail m => Sig -> Pos -> Prot -> [Term] ->
                [Term] -> SExpr Pos -> m Conj
loadUsedVars sig pos prot vars unbound x =
  do
    as <- loadConjunction sig pos prot vars x
    -- Compute the free variables in the conjunction
    let f vars (_, form) = aFreeVars vars form
    case unbound L.\\ foldl f [] as of
      [] -> return as
      (v : _) -> fail (shows (annotation x) (showst v " not used"))

-- Load a conjunction of atomic formulas, but don't ensure that all
-- declared variables are used.

loadLiberalVars :: MonadFail m => Sig -> Pos -> Prot -> [Term] ->
                [Term] -> SExpr Pos -> m Conj
loadLiberalVars sig pos prot vars _ x =
  do
    as <- loadConjunction sig pos prot vars x
    return as

-- Load a conjunction of atomic formulas

loadConjunction :: MonadFail m => Sig -> Pos -> Prot -> [Term] ->
                   SExpr Pos -> m Conj
loadConjunction sig _ p kvars (L pos (S _ "and" : xs)) =
  loadConjuncts sig pos p kvars xs []
loadConjunction sig top p kvars x =
  do
    (pos, a) <- loadPrimary sig top p kvars x
    return [(pos, a)]

loadConjuncts :: MonadFail m => Sig -> Pos -> Prot -> [Term] ->
                 [SExpr Pos] -> Conj -> m Conj
loadConjuncts _ _ _ _ [] rest = return (reverse rest)
loadConjuncts sig top p kvars (x : xs) rest =
  do
    (pos, a) <- loadPrimary sig top p kvars x
    loadConjuncts sig top p kvars xs ((pos, a) : rest)

-- Load the atomic formulas

loadPrimary :: MonadFail m => Sig -> Pos -> Prot -> [Term] ->
               SExpr Pos -> m (Pos, AForm)
loadPrimary sig _ _ kvars (L pos [S _ "=", x, y]) =
  do
    t <- loadTerm sig kvars False x
    t' <- loadTerm sig kvars False y
    case isStrdVar t == isStrdVar t' of
      True -> return (pos, Equals t t')
      False -> fail (shows pos "Sort mismatch in equality")
loadPrimary sig _ _ kvars (L pos [S _ "component", x, y]) =
  do
    t <- loadTerm sig kvars False x
    t' <- loadTerm sig kvars False y
    case isStrdVar t || isStrdVar t' of
      True -> fail (shows pos "Strand variable in component formula")
      False -> return (pos, Component t t')
loadPrimary sig _ _ kvars (L pos [S _ "non", x]) =
  do
    t <- loadAlgTerm sig kvars x
    return (pos, Non t)
loadPrimary sig _ _ kvars (L pos [S _ "pnon", x]) =
  do
    t <- loadAlgTerm sig kvars x
    return (pos, Pnon t)
loadPrimary sig _ _ kvars (L pos [S _ "uniq", x]) =
  do
    t <- loadAlgTerm sig kvars x
    return (pos, Uniq t)
loadPrimary sig _ _ kvars (L pos [S _ "uniq-at", x, y, z]) =
  do
    t <- loadAlgTerm sig kvars x
    t' <- loadNodeTerm sig kvars y z
    return (pos, UniqAt t t')
loadPrimary sig _ _ kvars (L pos [S _ "ugen", x]) =
  do
    t <- loadAlgTerm sig kvars x
    return (pos, Ugen t)
loadPrimary sig _ _ kvars (L pos [S _ "ugen-at", x, y, z]) =
  do
    t <- loadAlgTerm sig kvars x
    t' <- loadNodeTerm sig kvars y z
    return (pos, UgenAt t t')
loadPrimary sig _ _ kvars (L pos [S _ "gen-st", x]) =
  do
    t <- loadAlgTerm sig kvars x
    return (pos, GenStV t)
loadPrimary sig _ _ kvars (L pos [S _ "conf", x]) =
  do
    t <- loadChanTerm sig kvars x
    return (pos, Conf t)
loadPrimary sig _ _ kvars (L pos [S _ "auth", x]) =
  do
    t <- loadChanTerm sig kvars x
    return (pos, Auth t)
loadPrimary sig _ _ kvars (L pos (S _ "fact" : S _ name : fs)) =
  do
    fs <- loadTerms sig kvars fs
    return (pos, AFact name fs)
loadPrimary sig _ _ kvars (L pos [S _ "comm-pr", w, x, y, z]) =
  do
    t <- loadNodeTerm sig kvars w x
    t' <- loadNodeTerm sig kvars y z
    return (pos, Commpair t t')
loadPrimary sig _ _ kvars (L pos [S _ "same-locn", w, x, y, z]) =
  do
    t <- loadNodeTerm sig kvars w x
    t' <- loadNodeTerm sig kvars y z
    return (pos, SameLocn t t')
loadPrimary sig _ _ kvars (L pos [S _ "state-node", w, x]) =
  do
    t <- loadNodeTerm sig kvars w x
    return (pos, StateNode t)
loadPrimary sig _ _ kvars (L pos [S _ "trans", w, x]) =
  do
    t <- loadNodeTerm sig kvars w x
    return (pos, Trans t)
loadPrimary sig _ _ kvars (L pos [S _ "leads-to", w, x, y, z]) =
  do
    t <- loadNodeTerm sig kvars w x
    t' <- loadNodeTerm sig kvars y z
    return (pos, LeadsTo t t')

loadPrimary sig _ _ kvars (L pos [S _ "prec", w, x, y, z]) =
  do
    t <- loadNodeTerm sig kvars w x
    t' <- loadNodeTerm sig kvars y z
    case fst t == fst t' of
      True -> fail (shows pos "Malformed pair -- nodes in same strand")
      False -> return (pos, Prec t t')
loadPrimary sig _ p kvars (L pos [S _ "p", Q _ name, x, N _ h]) =
  do
    r <- lookupRole pos p name
    t <- loadStrdTerm sig kvars x
    case h <= 0 || h > length (rtrace r) of
      True -> fail (shows pos "Bad length")
      False -> return (pos, Length r t (indxOfInt h))
loadPrimary sig _ p kvars (L pos [S _ "p", Q _ name, x, ht]) =
  do
    r <- lookupRole pos p name
    t <- loadStrdTerm sig kvars x
    h <- loadTerm sig kvars False ht
    return (pos, Length r t h)
loadPrimary sig _ p kvars (L pos [S _ "p", Q _ name, Q var x, y, z]) =
  do
    r <- lookupRole pos p name
    v <- loadAlgChanTerm sig (rvars r) (S var x)
    s <- loadStrdTerm sig kvars y
    t <- loadAlgChanTerm sig kvars z
    case isVar v of
      False -> fail (shows pos ("Bad parameter -- not a variable" ++ (show v)))
      True ->
        case firstOccurs v r of
          Just i -> return (pos, Param r v (i + 1) s t)
          Nothing ->
            fail (shows pos ("parameter " ++ x ++ " not in role " ++ name))
loadPrimary _ _ _ _ (L pos (S _ "p" : Q _ name : _)) =
  fail (shows pos ("Bad protocol specific formula for role " ++ name))
loadPrimary _ _ _ _ (L pos (S _ pred : _)) =
  fail (shows pos ("Bad formula for predicate " ++ pred))
loadPrimary _ pos _ _ _ = fail (shows pos "Bad formula")

-- Load a term and make sure it does not have sort strd, indx, locn, or chan

loadAlgTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term
loadAlgTerm sig ts x =
  do
    t <- loadTerm sig ts False x
    case isStrdVar t || isIndxVar t || isIndxConst t || isChan t || isLocn t of
      True -> fail (shows (annotation x) "Expecting an algebra term")
      False -> return t
-- Load a term and make sure it does not have sort strd, or indx

loadAlgChanTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term
loadAlgChanTerm sig ts x =
  do
    t <- loadTerm sig ts False x
    case isStrdVar t || isIndxVar t || isIndxConst t of
      True -> fail (shows (annotation x)
                    "Expecting an algebra term or a channel")
      False -> return t

-- Load a term and make sure it has sort chan

loadChanTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term
loadChanTerm sig ts x =
  do
    t <- loadTerm sig ts False x
    case isChan t of
      True -> return t
      False -> fail (shows (annotation x) "Expecting a channel variable")

-- Load a term and make sure it has sort strd

loadStrdTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term
loadStrdTerm sig ts x =
  do
    t <- loadTerm sig ts False x
    case isStrdVar t of
      True -> return t
      False -> fail (shows (annotation x) "Expecting a strand variable")

-- Load a term and make sure it has sort indx

loadIndxTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term
loadIndxTerm sig ts x =
  do
    t <- loadTerm sig ts False x
    case isIndxVar t of
      True -> return t
      False ->
        case isIndxConst t of
          True -> return t
          False -> fail (shows (annotation x) "Expecting an indx variable")

-- Load a term and make sure it describes a node

loadNodeTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos ->
                SExpr Pos -> m NodeTerm
loadNodeTerm sig ts x (N _ i) | i >= 0 =
  do
    t <- loadStrdTerm sig ts x
    return (t, indxOfInt i)
loadNodeTerm sig ts x v =
  do
    t <- loadStrdTerm sig ts x
    t' <- loadIndxTerm sig ts v
    return (t, t')

-- Role specific check

termVars :: Term -> [Term]
termVars t = addVars [] t

allBound :: [Term] -> Term -> Bool
allBound unbound t =
  L.all (flip L.notElem unbound) (termVars t)

-- Returns variables in unbound that are not role specific

roleSpecific :: MonadFail m => [Term] -> (Pos, AForm) -> m [Term]
roleSpecific unbound (_, Length _ z _) =
  return $ L.delete z unbound
roleSpecific unbound (pos, Param _ _ _ z t)
  | L.notElem z unbound = return $ unbound L.\\ termVars t
  | otherwise = fail (shows pos "Unbound variable in parameter predicate")
roleSpecific unbound (pos, Prec (z, _) (z', _))
  | L.notElem z unbound && L.notElem z' unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in prec")
roleSpecific unbound (pos, Non t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in non")
roleSpecific unbound (pos, Pnon t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in pnon")
roleSpecific unbound (pos, Uniq t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in uniq")
roleSpecific unbound (pos, UniqAt t (z, _))
  | allBound unbound t && L.notElem z unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in uniq-at")
roleSpecific unbound (pos, Ugen t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in ugen")
roleSpecific unbound (pos, UgenAt t (z, _))
  | allBound unbound t && L.notElem z unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in ugen-at")
roleSpecific unbound (pos, GenStV t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in gen-st")
roleSpecific unbound (pos, Conf t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in conf")
roleSpecific unbound (pos, Auth t)
  | allBound unbound t = return unbound
  | otherwise = fail (shows pos "Unbound variable in auth")
roleSpecific unbound (pos, AFact _ fs)
  | all (allBound unbound) fs = return unbound
  | otherwise = fail (shows pos "Unbound variable in fact")
roleSpecific unbound (pos, Commpair (z, _) (z', _))
  | L.notElem z unbound && L.notElem z' unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in comm-pr")
roleSpecific unbound (pos, LeadsTo (z, _) (z', _))
  | L.notElem z unbound && L.notElem z' unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in leads-to")
roleSpecific unbound (pos, StateNode (z, _))
  | L.notElem z unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in state-node")
roleSpecific unbound (pos, Trans (z, _))
  | L.notElem z unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in trans")
roleSpecific unbound (pos, SameLocn (z, i) (z', i'))
  | L.notElem z unbound && L.notElem z' unbound &&
    L.notElem i unbound && L.notElem i' unbound = return unbound
  | otherwise = fail (shows pos "Unbound variable in same-locn")

roleSpecific unbound (pos, Equals t t')
  | isStrdVar t && isStrdVar t' =
    case L.notElem t unbound && L.notElem t' unbound of
      True -> return unbound
      False -> fail (shows pos "Unbound variable in equals")
  | isStrdVar t = fail (shows pos "Type mismatch in equals")
  | isStrdVar t' = fail (shows pos "Type mismatch in equals")
  | allBound unbound t && allBound unbound t' = return unbound
  | otherwise = fail (shows pos "Unbound variable in equals")

roleSpecific unbound (pos, Component t t')
  | isStrdVar t && isStrdVar t' =
    case L.notElem t unbound && L.notElem t' unbound of
      True -> return unbound
      False -> fail (shows pos "Unbound variable in component")
  | isStrdVar t = fail (shows pos "Type mismatch in component")
  | isStrdVar t' = fail (shows pos "Type mismatch in component")
  | allBound unbound t && allBound unbound t' = return unbound
  | otherwise = fail (shows pos "Unbound variable in component")

-- Remove unbound message variables that occur in a fact
factSpecific :: [Term] -> (Pos, AForm) -> [Term]
factSpecific unbound (_, AFact _ fs) =
  unbound L.\\ foldl addVars [] (L.filter (not . isStrdVar) fs)
factSpecific unbound _ = unbound