1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
module ContractProver
  ( addPreConditions, verifyPostConditions, verifyPreConditions
  ) where

import Control.Monad               ( unless )
import Contract.Names
import Control.Monad.IO.Class      ( liftIO )
import Control.Monad.Trans.Class   ( lift )
import Control.Monad.Trans.State   ( evalStateT, gets, put )
import Data.List                   ( elemIndex, find, init, intercalate
                                   , maximum, minimum )
import Data.Maybe                  ( isJust )

import FlatCurry.Annotated.Goodies
import FlatCurry.Annotated.Types
import FlatCurry.Show              ( showCurryType )
import FlatCurry.Types             ( showQName )
import Language.SMTLIB.Goodies
import qualified Language.SMTLIB.Types as SMT

import CheckSMT
import Common
import Curry2SMT
import FlatCurry.Typed.Build
import FlatCurry.Typed.Read
import FlatCurry.Typed.Simplify
import FlatCurry.Typed.Types
import ToolOptions
import TransState
import VerifierState

------------------------------------------------------------------------
-- Try to verify postconditions: If an operation `f` has a postcondition,
-- a proof for the validity of the postcondition is extracted.
-- If the proof is not successful, a postcondition check is added to `f`.
verifyPostConditions :: TAProg -> VStateM TAProg
verifyPostConditions prog = do
  postconds <- gets $ postConds . trInfo
  -- Operations with postcondition checks:
  let fdecls = progFuncs prog
  newfuns <- provePostConds postconds fdecls
  return $ updProgFuncs (const newfuns) prog
 where
  provePostConds []           fdecls = return fdecls
  provePostConds (pof : pofs) fdecls =
    provePostCondition pof fdecls >>= provePostConds pofs

provePostCondition :: TAFuncDecl -> [TAFuncDecl] -> VStateM [TAFuncDecl]
provePostCondition postfun allfuns = do
  maybe (do liftIO $ putStrLn $ "Postcondition: " ++ pcname ++ "\n" ++
                                "Operation of this postcondition not found!"
            return allfuns)
        (\checkfun -> evalStateT (provePC (simpFuncDecl checkfun))
                                 emptyTransState)
        (find (\fd -> toPostCondName (snd (funcName fd)) ==
                      decodeContractName pcname)
              allfuns)
 where
  pcname = snd (funcName postfun)

  provePC :: TAFuncDecl -> TransStateM [TAFuncDecl]
  provePC checkfun = do
    let (postmn,postfn) = funcName postfun
        mainfunc        = snd (funcName checkfun)
        orgqn           = (postmn, reverse (drop 5 (reverse postfn)))
        farity = funcArity checkfun
        ftype  = funcType checkfun
        targsr = zip [1..] (argTypes ftype ++ [resultType ftype])
    bodyformula     <- extractPostConditionProofObligation [1 .. farity]
                         (farity+1) (funcName checkfun) (funcRule checkfun)
    precondformula  <- preCondExpOf orgqn (init targsr)
    postcondformula <- applyFunc postfun targsr >>= pred2SMT
    let title = "verify postcondition of '" ++ mainfunc ++ "'..."
    lift $ printWhenIntermediate $ "Trying to " ++ title
    mbsmt <- checkPostCon ("SMT script to " ++ title)
                          (tand [precondformula, bodyformula])
                          true postcondformula
    maybe (lift $ do
             printWhenStatus $ mainfunc ++ ": POSTCOND CHECK ADDED"
             addPostCondToStats mainfunc False
             return $ map (addPostConditionTo (funcName postfun)) allfuns)
          (\proof -> lift $ do
             printWhenStatus $ mainfunc ++ ": POSTCONDITION VERIFIED"
             whenOption optStoreProof $ liftIO $
               writeFile ("PROOF_" ++ showQNameNoDots orgqn ++ "_" ++
                          "SatisfiesPostCondition.smt") proof
             addPostCondToStats mainfunc True
             return allfuns)
          mbsmt

-- If the function declaration is the declaration of the given function name,
-- decorate it with a postcondition check.
addPostConditionTo :: QName -> TAFuncDecl -> TAFuncDecl
addPostConditionTo pfname fdecl = let fn = funcName fdecl in
  if toPostCondQName fn == pfname
    then updFuncBody (const (addPostConditionCheck fn (funcRule fdecl))) fdecl
    else fdecl

-- Adds a postcondition check to a program rule of a given operation.
addPostConditionCheck :: QName -> TARule -> TAExpr
addPostConditionCheck _ (AExternal _ _) =
  error $ "Trying to add postcondition to external function!"
addPostConditionCheck qf@(mn,fn) (ARule ty lhs rhs) = --ALit boolType (Intc 42)
  AComb ty FuncCall
    ((mn, "checkPostCond_" ++ type2ID tt ++ "_" ++ type2ID (annExpr rhs)),
     FuncType ty (FuncType (FuncType ty boolType)
                           (FuncType stringType (FuncType tt ty))))
    [ rhs
    , AComb boolType (FuncPartCall 1) (toPostCondQName qf, ty) args
    , string2TAFCY fn
    , tupleExpr args
    ]
 where
  args = map (\ (i,t) -> AVar t i) lhs
  tt = tupleType (map annExpr args)

extractPostConditionProofObligation :: [Int] -> Int -> QName -> TARule
                                    -> TransStateM SMT.Term
extractPostConditionProofObligation _ _ _ (AExternal _ s) =
  return $ tcomb ("External: " ++ s) []
extractPostConditionProofObligation args resvar fname
                                    (ARule ty orgargs orgexp) = do
  let exp    = rnmAllVars renameRuleVar orgexp
      rtype  = resType (length orgargs) (stripForall ty)
  put $ makeTransState
            (maximum (resvar : allVars exp) + 1)
            ((resvar, rtype, Just (fname, 0, 1))
              : map (\(i,x,y) -> (x, y, Just (fname, i, 1)))
                (zip3 [1..] args $ map snd orgargs))
  binding2SMT True (resvar,exp)
 where
  maxArgResult = maximum (resvar : args)
  renameRuleVar r = maybe (r + maxArgResult + 1)
                          (args!!)
                          (elemIndex r (map fst orgargs))

  resType n te =
    if n==0
      then te
      else case te of
             FuncType _ rt -> resType (n-1) rt
             _             -> error $ "Internal errror: resType: " ++ show te

---------------------------------------------------------------------------
-- Try to verify preconditions: If an operation `f` occurring in some
-- right-hand side has a precondition, a proof for the validity of
-- this precondition is extracted.
-- If the proof is not successful, a precondition check is added to this call.
verifyPreConditions :: TAProg -> VStateM TAProg
verifyPreConditions prog = do
  newfuns  <- mapM provePreCondition $ progFuncs prog
  return (updProgFuncs (const newfuns) prog)

provePreCondition :: TAFuncDecl -> VStateM TAFuncDecl
provePreCondition fdecl = do
  printWhenIntermediate $
    "Operation to be checked: " ++ snd (funcName fdecl)
  newrule <- optPreConditionInRule (funcName fdecl) (funcRule fdecl)
  return (updFuncRule (const newrule) fdecl)

optPreConditionInRule :: QName -> TARule -> VStateM TARule
optPreConditionInRule _ rl@(AExternal _ _)            = return rl
optPreConditionInRule qn@(_,fn) (ARule rty rargs rhs) = do
  let targs = zip [1..] (map snd rargs)
      st = makeTransState
            (maximum (0 : map fst rargs ++ allVars rhs) + 1)
            (map (\(i,(x,y)) -> (x, y, Just (qn, i, 0))) (zip [1..] rargs))
  (flip evalStateT) st $ do
    -- compute precondition of operation:
    precondformula <- preCondExpOf qn targs
    setAssertion precondformula
    newrhs <- optPreCondInExp rhs
    return $ ARule rty rargs newrhs
 where
  optPreCondInExp exp = case exp of
    AComb ty ct (qf,tys) args -> do
      precond  <- getAssertion
      nargs    <- mapM optPreCondInExp args
      preconds <- lift $ gets $ preConds . trInfo
      if toPreCondQName qf `elem` map funcName preconds
        then do
          lift $ printWhenIntermediate $ "Checking call to " ++ snd qf
          (bs,_)   <- normalizeArgs nargs
          setNameOfVars qf $ map fst bs
          bindexps <- mapM (binding2SMT True) bs
          precondcall <- preCondExpOf qf
                           (zip (map fst bs) (map annExpr args))
          -- TODO: select from 'bindexps' only demanded argument positions
          let title = "SMT script to verify precondition of '" ++ snd qf ++
                      "' in function '" ++ fn ++ "'"
          valid <- checkPreCon title precond (tand bindexps) precondcall
                     fn (map fst rargs)
          if valid == Just True
            then lift $ do
              printWhenStatus $
                fn ++ ": PRECONDITION OF '" ++ snd qf ++ "': VERIFIED"
              addPreCondToStats (snd qf ++ "(" ++ fn ++ ")") True
              return $ AComb ty ct (toNoCheckQName qf, tys) nargs
            else lift $ do
              printWhenStatus $
                fn ++ ": PRECOND CHECK ADDED TO '" ++ snd qf ++ "'"
              addPreCondToStats (snd qf ++ "("++ fn ++ ")") False
              return $ AComb ty ct (qf,tys) nargs
        else return $ AComb ty ct (qf,tys) nargs
    ACase ty ct e brs -> do
      ne <- optPreCondInExp e
      freshvar <- getFreshVar
      be <- binding2SMT True (freshvar,ne)
      addToAssertion be
      addVarTypes [ (freshvar, annExpr ne) ]
      nbrs <- mapM (optPreCondInBranch freshvar) brs
      return $ ACase ty ct ne nbrs
    AOr ty e1 e2 -> do
      ne1 <- optPreCondInExp e1
      ne2 <- optPreCondInExp e2
      return $ AOr ty ne1 ne2
    ALet ty bs e -> do
      nes <- mapM optPreCondInExp (map snd bs)
      ne  <- optPreCondInExp e
      return $ ALet ty (zip (map fst bs) nes) ne
    AFree ty fvs e -> do
      ne <- optPreCondInExp e
      return $ AFree ty fvs ne
    ATyped ty e et -> do
      ne <- optPreCondInExp e
      return $ ATyped ty ne et
    _ -> return exp

  optPreCondInBranch dvar branch = do
    ABranch p e <- renamePatternVars branch
    addToAssertion (tvar dvar =% pat2SMT p)
    ne <- optPreCondInExp e
    return (ABranch p ne)

-- Rename argument variables of constructor pattern
renamePatternVars :: TABranchExpr -> TransStateM TABranchExpr
renamePatternVars (ABranch p e) =
  if isConsPattern p
    then do
      fv <- getFreshVarIndex
      let args = map fst (patArgs p)
          minarg = minimum (0 : args)
          maxarg = maximum (0 : args)
          rnm i = if i `elem` args then i - minarg + fv else i
          nargs = map (\ (v,t) -> (rnm v,t)) (patArgs p)
      setFreshVarIndex (fv + maxarg - minarg + 1)
      addVarTypes nargs
      return $ ABranch (updPatArgs (map (\ (v,t) -> (rnm v,t))) p)
                       (rnmAllVars rnm e)
    else return $ ABranch p e

---------------------------------------------------------------------------
-- Add (non-trivial) preconditions:
-- If an operation `f` has some precondition `f'pre`,
-- replace the rule `f xs = rhs` by the following rules:
--
--     f xs = checkPreCond (f'NOCHECK xs) (f'pre xs) "f" xs
--     f'NOCHECK xs = rhs
addPreConditions :: TAProg -> VStateM TAProg
addPreConditions prog = do
  newfuns  <- mapM addPreCondition (progFuncs prog)
  return $ updProgFuncs (const (concat newfuns)) prog
 where
  addPreCondition fdecl@(AFunc qf ar vis fty rule) = do
    preconds <- gets $ preConds . trInfo
    return $
      if toPreCondQName qf `elem` map funcName preconds
        then let newrule = checkPreCondRule qf rule
             in [updFuncRule (const newrule) fdecl,
                 AFunc (toNoCheckQName qf) ar vis fty rule]
        else [fdecl]

  checkPreCondRule :: QName -> TARule -> TARule
  checkPreCondRule qn (ARule rty rargs _) =
    ARule rty rargs (addPreConditionCheck rty FuncCall qn rty
                       (map (\ (v,t) -> AVar t v) rargs))
  checkPreCondRule qn (AExternal _ _) = error $
    "addPreConditions: cannot add precondition to external operation '" ++
    snd qn ++ "'!"

-- Adds a precondition check to a original call of the form
-- `AComb ty ct (qf,tys) args`.
addPreConditionCheck :: TypeExpr -> CombType -> QName -> TypeExpr
                     -> [TAExpr] -> TAExpr
addPreConditionCheck ty ct qf@(mn,fn) tys args =
  AComb ty FuncCall
    ((mn, "checkPreCond_" ++ type2ID tt),
     FuncType ty (FuncType boolType (FuncType stringType (FuncType tt ty))))
    [ AComb ty ct (toNoCheckQName qf,tys) args
    , AComb boolType ct (toPreCondQName qf, pctype) args
    , string2TAFCY fn
    , tupleExpr args
    ]
 where
  argtypes = map annExpr args
  tt       = tupleType argtypes
  pctype   = foldr FuncType boolType argtypes

-- Translate a type expression into an identifier which is the suffix
-- of `checkPreCond` which is a type-specialized instance.
type2ID :: TypeExpr -> String
type2ID te = case te of
  TCons (mn,tc) tes | mn == "Prelude" && null tes
    -> intercalate "_" (tc2ID tc : map type2ID tes)
  _ -> "Any"
 where
  tc2ID tc | tc == "[]" = "List"
           | tc == "()" = "Unit"
           | otherwise  = tc

---------------------------------------------------------------------------
-- Auxiliaries:

--- Transform a qualified name into a name of the corresponding function
--- without precondition checking by adding the suffix "'NOCHECK".
toNoCheckQName :: (String,String) -> (String,String)
toNoCheckQName (mn,fn) = (mn, fn ++ "'NOCHECK")

-- Shows a qualified name by replacing all dots by underscores.
showQNameNoDots :: QName -> String
showQNameNoDots = map (\c -> if c=='.' then '_' else c) . showQName

---------------------------------------------------------------------------