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
module VerifierState where

import Control.Applicative         ( when )
import Control.Monad.Trans.Class   ( lift )
import Control.Monad.Trans.State   ( StateT, get, gets, modify, put )
import Data.List                   ( find, isSuffixOf )

import Contract.Names              ( isNonFailName, isPreCondName
                                   , isPostCondName )
import FlatCurry.Annotated.Goodies

import FlatCurry.Typed.Types
import ToolOptions

---------------------------------------------------------------------------
-- Some global information used by the verification process:
data VerifyInfo = VerifyInfo
  { toolOpts      :: Options      -- options passed to the tool
  , allFuncs      :: [TAFuncDecl] -- all defined operations
  , preConds      :: [TAFuncDecl] -- all precondition operations
  , postConds     :: [TAFuncDecl] -- all postcondition operations
  , nfConds       :: [TAFuncDecl] -- all non-failure condition operations
  }

initVerifyInfo :: Options -> VerifyInfo
initVerifyInfo opts = VerifyInfo opts [] [] [] []

addFunsToVerifyInfo' :: [TAFuncDecl] -> VerifyInfo -> VerifyInfo
addFunsToVerifyInfo' fdecls ti =
  ti { allFuncs  = fdecls    ++ allFuncs  ti
     , preConds  = preconds  ++ preConds  ti
     , postConds = postconds ++ postConds ti
     , nfConds   = nfconds   ++ nfConds   ti
     }
 where
  -- Precondition operations:
  preconds  = filter (isPreCondName  . snd . funcName) fdecls
  -- Postcondition operations:
  postconds = filter (isPostCondName . snd . funcName) fdecls
  -- Non-failure condition operations:
  nfconds   = filter (isNonFailName  . snd . funcName) fdecls

--- Is an operation name the name of a contract or similar?
isContractOp :: QName -> Bool
isContractOp (_,fn) = isNonFailName fn || isPreCondName fn || isPostCondName fn

--- Is a function declaration a property?
isProperty :: TAFuncDecl -> Bool
isProperty fdecl =
  resultType (funcType fdecl)
    `elem` map (\tc -> TCons tc [])
               [("Test.Prop","Prop"),("Test.EasyCheck","Prop")]

---------------------------------------------------------------------------
-- The global state of the verification process keeps some
-- statistical information and the programs that are already read (to
-- avoid multiple readings).
data VState = VState
  { trInfo       :: VerifyInfo        -- information used by the verifier
  , failedFuncs  :: [(String,String)] -- partially defined operations
                                      -- and their failing reason
  , numAllFuncs  :: Int               -- number of analyzed operations
  , numNFCFuncs  :: Int               -- number of operations with non-trivial
                                      -- non-failing conditions
  , numPatTests  :: Int               -- number of missing pattern tests
  , numFailTests :: Int               -- number of tests of failure calls
  , uPreCond     :: [String]          -- unverified preconditions
  , vPreCond     :: [String]          -- verified preconditions
  , uPostCond    :: [String]          -- unverified postconditions
  , vPostCond    :: [String]          -- verified postconditions
  , currTAProgs  :: [TAProg]          -- already read typed FlatCurry programs
  }

initVState :: Options -> VState
initVState opts = VState (initVerifyInfo opts) [] 0 0 0 0 [] [] [] [] []

-- The type of the state monad containing the verification state.
type VStateM a = StateT VState IO a

---------------------------------------------------------------------------
-- Modifies the options with the given function.
modifyOptions :: (Options -> Options) -> VStateM ()
modifyOptions f = do
  vstate <- get
  let vinfo  = trInfo vstate
      vinfo' = vinfo { toolOpts = f $ toolOpts vinfo}
  put vstate { trInfo = vinfo' }

-- Gets a specific option.
getOption :: (Options -> a) -> VStateM a
getOption f = gets (toolOpts . trInfo) >>= return . f

-- Gets an option and only executes the given monad if the given predicate
-- evaluates to true for that option.
evalOption :: (Options -> a) -> (a -> Bool) -> VStateM () -> VStateM ()
evalOption f b m = getOption f >>= flip when m . b

-- Gets an option and only executes the given monad if this option is true.
whenOption :: (Options -> Bool) -> VStateM () -> VStateM ()
whenOption f = evalOption f id

---------------------------------------------------------------------------
printWhenStatus :: String -> VStateM ()
printWhenStatus s = evalOption optVerb (> 0) $ printCP s

printWhenIntermediate :: String -> VStateM ()
printWhenIntermediate s = evalOption optVerb (> 1) $ printCP s

printWhenAll :: String -> VStateM ()
printWhenAll s = evalOption optVerb (> 2) $ printCP s

printCP :: String -> VStateM ()
printCP s = lift $ putStrLn $ "INFO: " ++ s

---------------------------------------------------------------------------
--- Shows the statistics for the failfree verification in human-readable
--- format.
showFailfreeStats :: VState -> String
showFailfreeStats vstate = unlines $
  [ "TESTED OPERATIONS        : " ++ show (numAllFuncs vstate)
  , "NONFAIL CONDITIONS       : " ++ show (numNFCFuncs vstate)
  , "TESTS OF MISSING PATTERNS: " ++ show (numPatTests vstate)
  , "TESTS OF NON-FAIL CALLS  : " ++ show (numFailTests vstate) ] ++
  showStat "POSSIBLY FAILING OPERATIONS" (failedFuncs vstate) ++
  if isVerifiedFailfree vstate
    then ["NON-FAILURE VERIFICATION SUCCESSFUL!"]
    else []
 where
  showStat t fs =
    if null fs
      then []
      else (t ++ ":") :
           map (\ (fn,reason) -> fn ++ " (" ++ reason ++ ")")
               (reverse fs)

--- Shows the statistics for the contract checking in human-readable format.
showContractStats :: VState -> String
showContractStats vstate =
 showStat "PRECONDITIONS : VERIFIED  " (vPreCond  vstate) ++
 showStat "PRECONDITIONS : UNVERIFIED" (uPreCond  vstate) ++
 showStat "POSTCONDITIONS: VERIFIED  " (vPostCond vstate) ++
 showStat "POSTCONDITIONS: UNVERIFIED" (uPostCond vstate) ++
 (if isVerifiedContracts vstate
    then "\nALL CONTRACTS VERIFIED!"
    else "")
where
  showStat t fs = if null fs then "" else "\n" ++ t ++ ": " ++ unwords fs

--- Shows the statistics in human-readable format.
showStats :: VStateM ()
showStats = do
  vstate <- get
  let opts     = toolOpts $ trInfo vstate
      notQuiet = optVerb opts > 0
  when ((notQuiet || not (isVerifiedFailfree vstate)) && optFailfree opts)
    $ lift $ putStr $ showFailfreeStats vstate
  when ((notQuiet || not (isVerifiedContracts vstate)) && optContract opts > 1)
    $ lift $ putStrLn $ showContractStats vstate

--- Are all non-failing properties verified?
isVerifiedFailfree :: VState -> Bool
isVerifiedFailfree vstate = null (failedFuncs vstate)

--- Are all contracts verified?
isVerifiedContracts :: VState -> Bool
isVerifiedContracts vstate = null (uPreCond vstate) && null (uPostCond vstate)

---------------------------------------------------------------------------
addFunsToVerifyInfo :: [TAFuncDecl] -> VStateM ()
addFunsToVerifyInfo fdecls = do
  ti <- gets trInfo
  modify $ \vstate -> vstate { trInfo = addFunsToVerifyInfo' fdecls ti }

--- Adds a possibly failing call in a functions and the called function.
addFailedFuncToStats :: String -> String -> VStateM ()
addFailedFuncToStats fn qfun =
  modify $ \vstate -> vstate { failedFuncs = (fn,qfun) : failedFuncs vstate }

--- Increments the number of all tested functions.
incNumAllInStats :: VStateM ()
incNumAllInStats =
  modify $ \vstate -> vstate { numAllFuncs = numAllFuncs vstate + 1 }

--- Increments the number of operations with nonfail conditions.
incNumNFCInStats :: VStateM ()
incNumNFCInStats =
  modify $ \vstate -> vstate { numNFCFuncs = numNFCFuncs vstate + 1 }

--- Increments the number of missing pattern tests.
incPatTestInStats :: VStateM ()
incPatTestInStats =
  modify $ \vstate -> vstate { numPatTests = numPatTests vstate + 1 }

--- Increments the number of test of possible failure calls.
incFailTestInStats :: VStateM ()
incFailTestInStats =
  modify $ \vstate -> vstate { numFailTests = numFailTests vstate + 1 }

--- Increments the number of preconditions. If the first argument is true,
--- a precondition has been verified.
addPreCondToStats :: String -> Bool -> VStateM ()
addPreCondToStats pc verified =
  if verified then modify $ \vst -> vst { vPreCond = pc : vPreCond vst }
              else modify $ \vst -> vst { uPreCond = pc : uPreCond vst }

--- Adds an operation to the already processed operations with postconditions.
--- If the second argument is true, the postcondition of this operation
--- has been verified.
addPostCondToStats :: String -> Bool -> VStateM ()
addPostCondToStats pc verified =
  if verified then modify $ \vst -> vst { vPostCond = pc : vPostCond vst }
              else modify $ \vst -> vst { uPostCond = pc : uPostCond vst }

--- Adds a new typed FlatCurry program to the state.
addProgsToState :: [TAProg] -> VStateM ()
addProgsToState progs =
  modify $ \vstate -> vstate { currTAProgs = currTAProgs vstate ++ progs }

---------------------------------------------------------------------------
--- Selects the type declaration of a type constructor from the state.
tdeclOf :: QName -> VStateM (Maybe TypeDecl)
tdeclOf tcons@(mn,_) = do
  vst <- get
  return $ maybe Nothing
           (\p -> find (\td -> typeName td == tcons) (progTypes p))
           (find (\p -> progName p == mn) (currTAProgs vst))

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