From c9de43c455751760eec77c0b2d1fd73305cb7d53 Mon Sep 17 00:00:00 2001
From: Orestis Melkonian <melkon.or@gmail.com>
Date: Sun, 25 Feb 2018 18:44:05 +0100
Subject: [PATCH] API: Error propagation

---
 .gitignore                    |   2 ++
 app/Main.hs                   |  20 ++++++++---------
 javawlp.cabal                 |   3 ++-
 src/API.hs                    |  40 ++++++++++++++++++++++++----------
 src/LogicIR/Backend/Z3/API.hs |  21 +++++++++---------
 src/LogicIR/Backend/Z3/Z3.hs  |   3 +--
 src/LogicIR/Expr.hs           |  21 ++++++++++--------
 src/LogicIR/Frontend/Java.hs  |  17 ++++++---------
 src/LogicIR/Pretty.hs         |   7 ------
 src/Model.hs                  |   7 ++++--
 src/Server.hs                 |  24 ++++++++++----------
 z3.log                        | Bin 761813 -> 0 bytes
 12 files changed, 90 insertions(+), 75 deletions(-)
 delete mode 100644 z3.log

diff --git a/.gitignore b/.gitignore
index 2647b48..77c9476 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@ tests/org/
 .stack-work/
 # Intellij IDEA
 .idea/
+# Z3
+z3.log
diff --git a/app/Main.hs b/app/Main.hs
index 9687543..4aac103 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -3,16 +3,16 @@ module Main where
 import Control.Monad
 import Data.Semigroup ((<>))
 import Options.Applicative
-import API (compareSpec, Mode (..), ParseMode (..))
 
+import API (Mode (..), ParseMode (..), compareSpec)
 import Server (runApi)
 
 -- | Command-line options.
 data Options = Options
-  { srcA      :: String
-  , srcB      :: String
-  , method1   :: String
-  , method2   :: String
+  { sourceA   :: String
+  , sourceB   :: String
+  , methodA   :: String
+  , methodB   :: String
   , runServer :: Bool
   , port      :: Int
   }
@@ -69,10 +69,10 @@ main = runMain =<< execParser (parseOptions `withInfo` "Java WLP")
 
 -- | Run.
 runMain :: Options -> IO ()
-runMain (Options srcA srcB methodA methodB runServer port) =
-  if runServer then
-    runApi port
+runMain (Options srcA srcB methA methB serverFlag portNo) =
+  if serverFlag then
+    runApi portNo
   else do
-    when (methodA == "_default_") $ fail "No files given."
-    response <- compareSpec Release File (srcA, methodA) (srcB, methodB)
+    when (methA == "_default_") $ fail "No files given."
+    response <- compareSpec Release File (srcA, methA) (srcB, methB)
     print response
diff --git a/javawlp.cabal b/javawlp.cabal
index 8aed071..b2eb980 100644
--- a/javawlp.cabal
+++ b/javawlp.cabal
@@ -74,8 +74,9 @@ library
                      , text
                      , http-types
                      , lens
+                     , deepseq
   default-language:    Haskell2010
-  ghc-options:         -Wall
+  -- ghc-options:         -Wall
 
 test-suite javawlp-tests
   type:                exitcode-stdio-1.0
diff --git a/src/API.hs b/src/API.hs
index 95afb88..56724a7 100644
--- a/src/API.hs
+++ b/src/API.hs
@@ -1,7 +1,9 @@
+{-# LANGUAGE ScopedTypeVariables #-}
 module API where
 
 import Control.Concurrent
 import Data.Maybe
+import Data.List.Split (splitOn)
 
 import Javawlp.Engine.HelperFunctions
 import Javawlp.Engine.Types
@@ -14,6 +16,9 @@ import LogicIR.Frontend.Java (javaExpToLExpr)
 import LogicIR.Null (lExprPreprocessNull)
 import Model
 
+import Control.DeepSeq
+import Control.Exception.Base
+
 -- | Data types.
 data Mode = Debug | Release deriving (Eq, Show)
 
@@ -66,11 +71,19 @@ getRes Debug    resp              resp'             =
 checkSpec :: ParseMode -> EquivImpl -> (Source, String) -> (FilePath, String) -> IO Response
 checkSpec pMode equivTo methodA methodB = do
     [m1, m2] <- mapM (parseMethod pMode) [methodA, methodB]
-    let (preL, preL') = methodDefToLExpr m1 m2 "pre"
-    preRes <- preL `equivTo` preL'
-    let (postL, postL') = methodDefToLExpr m1 m2 "post"
-    postRes <- postL `equivTo` postL'
-    return $ preRes <> postRes
+    res <- methodDefToLExpr m1 m2 "pre"
+    case res of
+      Left e ->
+        return $ ErrorResponse $ head $ splitOn "CallStack" e
+      Right (preL, preL') -> do
+        preRes <- preL `equivTo` preL'
+        res' <- methodDefToLExpr m1 m2 "post"
+        case res' of
+          Left e' ->
+            return $ ErrorResponse e'
+          Right (postL, postL') -> do
+            postRes <- postL `equivTo` postL'
+            return $ preRes <> postRes
 
 --------------------------------------------------------------------------------
 
@@ -88,16 +101,19 @@ parseMethod pMode (src, name) = do
     -- return the relevant data
     return (decls, mbody, env)
 
-methodDefToLExpr :: MethodDef -> MethodDef -> String -> (LExpr, LExpr)
+methodDefToLExpr :: MethodDef -> MethodDef -> String -> IO (Either String (LExpr, LExpr))
 methodDefToLExpr m1@(decls1, _, env1) m2@(decls2, _, env2) name = do
     -- get pre/post condition
     let (e1, e2) = (extractCond m1 name, extractCond m2 name)
-    let (l1, l2) = (javaExpToLExpr e1 env1 decls1, javaExpToLExpr e2 env2 decls2)
-    -- preprocess "a == null" to "isNull(a)"
-    let (l, l') = (lExprPreprocessNull l1, lExprPreprocessNull l2)
-    (l, l')
-        where extractCond :: MethodDef -> String -> Exp
-              extractCond m n = extractExpr $ getMethodCalls m n
+    res :: Either SomeException (LExpr, LExpr) <-
+      try . evaluate . force $ (javaExpToLExpr e1 env1 decls1, javaExpToLExpr e2 env2 decls2)
+    return $ case res of
+      Left e ->
+        Left $ show e
+      Right (l, l') ->
+        Right (lExprPreprocessNull l, lExprPreprocessNull l')
+    where extractCond :: MethodDef -> String -> Exp
+          extractCond m n = extractExpr $ getMethodCalls m n
 
 -- Get a list of all calls to a method of a specific name from a method definition.
 getMethodCalls :: MethodDef -> String -> [MethodInvocation]
diff --git a/src/LogicIR/Backend/Z3/API.hs b/src/LogicIR/Backend/Z3/API.hs
index cce27f7..f761068 100644
--- a/src/LogicIR/Backend/Z3/API.hs
+++ b/src/LogicIR/Backend/Z3/API.hs
@@ -1,20 +1,17 @@
 module LogicIR.Backend.Z3.API where
 
-import Z3.Monad hiding (Model)
 import qualified Z3.Base as Z3
-import Z3.Opts
+import Z3.Monad hiding (Model)
 
-import Data.String
 import Control.Exception.Base (tryJust)
-import Control.Monad (forM, forM_, when)
-import Control.Monad.Trans (liftIO)
+import Control.Monad (forM)
+import Data.String
 import Data.Maybe (fromJust)
-import Data.Monoid ((<>))
 import qualified Data.Map as M
 
-import Model
-import LogicIR.Expr (LExpr)
 import LogicIR.Backend.Z3.Z3
+import LogicIR.Expr (LExpr)
+import Model
 
 -- | Determine the equality of two method's pre/post conditions.
 equivalentTo :: LExpr -> LExpr -> IO Response
@@ -34,10 +31,10 @@ equivalentTo lexpr lexpr' = do
 
 -- | Check if two Z3 AST's are equivalent.
 equivalentToZ3 :: Z3 FreeVars -> Z3 AST -> Z3 AST -> IO Response
-equivalentToZ3 freeVars ast1' ast2' =
+equivalentToZ3 fVars ast1' ast2' =
   tryZ3 $ do
     -- Setup
-    fv <- freeVars
+    fv <- fVars
     ast1 <- ast1'
     ast2 <- ast2'
     astEq <- mkEq ast1 ast2
@@ -74,7 +71,7 @@ equivalentToZ3 freeVars ast1' ast2' =
           f <- snd <$> evalAST fv m (k ++ "?length", lenName)
           let len = case f of
                 (IntVal i) -> i
-                _ -> error "non-int length"
+                _          -> error "non-int length"
           -- Iterate array "points"
           modelVals <- forM [0..(len-1)] (\i -> do
             indexAST <- mkInteger $ toInteger i
@@ -87,6 +84,7 @@ equivalentToZ3 freeVars ast1' ast2' =
           return (k, fromString v' :: ModelVal)
 
 -- | Z3 try evaluation with timeout.
+tryZ3 :: Z3 a -> IO a
 tryZ3 = evalZ3With Nothing (  opt "timeout" (5000 :: Integer)
                            +? opt "model_validate" True
                            +? opt "well_sorted_check" True
@@ -95,6 +93,7 @@ tryZ3 = evalZ3With Nothing (  opt "timeout" (5000 :: Integer)
                            )
 
 -- | Sequence tactics.
+(-->) :: String -> String -> Z3 Z3.Tactic
 (-->) t t' = do
   tt <- mkTactic t
   tt' <- mkTactic t'
diff --git a/src/LogicIR/Backend/Z3/Z3.hs b/src/LogicIR/Backend/Z3/Z3.hs
index 6d5ea09..f77c055 100644
--- a/src/LogicIR/Backend/Z3/Z3.hs
+++ b/src/LogicIR/Backend/Z3/Z3.hs
@@ -1,13 +1,12 @@
 {-# LANGUAGE OverloadedStrings #-}
 module LogicIR.Backend.Z3.Z3 where
 
-import Control.Monad.Trans (liftIO)
 import qualified Data.Map as M
 import Z3.Monad
 
 import LogicIR.Expr
 import LogicIR.Fold
-import LogicIR.Parser
+import LogicIR.Parser ()
 
 lExprToZ3Ast :: LExpr -> Z3 AST
 lExprToZ3Ast = foldLExpr lExprToZ3AstAlgebra
diff --git a/src/LogicIR/Expr.hs b/src/LogicIR/Expr.hs
index 9a6c2e9..8645935 100644
--- a/src/LogicIR/Expr.hs
+++ b/src/LogicIR/Expr.hs
@@ -1,26 +1,29 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric  #-}
 module LogicIR.Expr where
 
-import Data.String
+import GHC.Generics
+import Control.DeepSeq
 
 -- | The primitive types are bool and int32.
 data Primitive = PBool
                | PInt
                | PReal
-               deriving (Show, Eq, Read)
+               deriving (Show, Eq, Read, Generic, NFData)
 
 -- | A Type can either be a primitive or an array type.
 data Type = TPrim Primitive
           | TArray Type
-          deriving (Show, Eq, Read)
+          deriving (Show, Eq, Read, Generic, NFData)
 
 -- | Typed + named variable.
 data Var = Var Type String
-         deriving (Show, Eq, Read)
+         deriving (Show, Eq, Read, Generic, NFData)
 
 -- | Unary operators.
 data LUnop = NNeg -- -n (numeric negation)
            | LNot -- !n (logical not)
-           deriving (Show, Eq, Read)
+           deriving (Show, Eq, Read, Generic, NFData)
 
 -- | Binary operators.
 data LBinop =
@@ -38,18 +41,18 @@ data LBinop =
             | CEqual -- a == b
             | CLess -- a < b
             | CGreater -- a > b
-            deriving (Show, Eq, Read)
+            deriving (Show, Eq, Read, Generic, NFData)
 
 -- | Quantifier operators.
 data QOp = QAll | QAny
-         deriving (Show, Eq, Read)
+         deriving (Show, Eq, Read, Generic, NFData)
 
 -- | Constants.
 data LConst = CBool Bool
             | CInt Int
             | CReal Double
             | CNil
-            deriving (Show, Eq, Read)
+            deriving (Show, Eq, Read, Generic, NFData)
 
 -- | Logical expressions.
 data LExpr = LConst LConst -- constant
@@ -61,7 +64,7 @@ data LExpr = LConst LConst -- constant
            | LArray Var LExpr -- array access
            | LIsnull Var -- var == null
            | LLen Var -- len(array)
-           deriving (Show, Eq, Read)
+           deriving (Show, Eq, Read, Generic, NFData)
 
 -- Needed for the ArrayModel key ordering in the Map in LogicIR.Backend.ModelGenerator
 instance Ord Var where
diff --git a/src/LogicIR/Frontend/Java.hs b/src/LogicIR/Frontend/Java.hs
index 1706600..c50458e 100644
--- a/src/LogicIR/Frontend/Java.hs
+++ b/src/LogicIR/Frontend/Java.hs
@@ -4,15 +4,12 @@ module LogicIR.Frontend.Java (javaExpToLExpr) where
 import Javawlp.Engine.Folds
 import Javawlp.Engine.HelperFunctions
 
-import Language.Java.Parser
 import Language.Java.Pretty
 import Language.Java.Syntax
-import Language.Java.Syntax.Types
 
 import Data.String
-import Data.Typeable
 import LogicIR.Expr
-import LogicIR.Parser
+import LogicIR.Parser ()
 
 javaExpToLExpr :: Exp -> TypeEnv -> [TypeDecl] -> LExpr
 javaExpToLExpr = foldExp javaExpToLExprAlgebra
@@ -89,7 +86,7 @@ javaExpToLExprAlgebra =
                           _ -> error $ "Unimplemented fMethodInv: " ++ show inv
               quantr method name rbegin rend bound expr =
                 let (begin, end) = (refold rbegin, refold rend)
-                    (i, arr) = (Var (TPrim PInt) bound, nameToVar name env decls)
+                    (i, _) = (Var (TPrim PInt) bound, nameToVar name env decls)
                 in case method of
                           "forallr" -> lquantr QAll i begin end expr
                           "existsr" -> lquantr QAny i begin end expr
@@ -99,13 +96,13 @@ javaExpToLExprAlgebra =
               refold expr =
                 foldExp javaExpToLExprAlgebra expr env decls
     fArrayAccess arrayIndex env decls =
-      case arrayIndex of -- TODO: type checking
+      case arrayIndex of
         ArrayIndex (ExpName name) [expr]
           -> LArray (nameToVar name env decls) (javaExpToLExpr expr env decls)
         _
           -> error $ "Multidimensional arrays are not supported: " ++ show arrayIndex
     fExpName name env decls =
-      case name of -- TODO: type checking + check implicit `this.name`
+      case name of
         Name [Ident a, Ident "length"] -> LLen $ nameToVar (Name [Ident a]) env decls
         _ -> LVar $ nameToVar name env decls
     fPostIncrement = error "fPostIncrement has side effects..."
@@ -114,7 +111,7 @@ javaExpToLExprAlgebra =
     fPreDecrement = error "fPreDecrement has side effects..."
     fPrePlus e = e
     fPreMinus e env decls = LUnop NNeg (e env decls)
-    fPreBitCompl e env decls = error "Bitwise operations not supported..."
+    fPreBitCompl _ _ _ = error "Bitwise operations not supported..."
     fPreNot e env decls = LUnop LNot (e env decls)
     fCast = error "fCast is not supported..." -- TODO: perhaps support cast for some types?
     fBinOp e1 op e2 env decls = -- TODO: type checking?
@@ -139,9 +136,9 @@ javaExpToLExprAlgebra =
             GThanE  -> (.>=)
             Equal   -> (.==)
             NotEq   -> (.!=)
-            _ -> error $ "Unsupported operation: " ++ show op
+            _       -> error $ "Unsupported operation: " ++ show op
     fInstanceOf = error "fInstanceOf is not supported..."
-    fCond c a b env decls = LIf (c env decls) (a env decls) (b env decls)
+    fCond c a b_ env decls = LIf (c env decls) (a env decls) (b_ env decls)
     fAssign = error "fAssign has side effects..."
     fLambda = error "fLambda should be handled by fMethodInv..."
     fMethodRef = undefined
diff --git a/src/LogicIR/Pretty.hs b/src/LogicIR/Pretty.hs
index 10df1c7..431b593 100644
--- a/src/LogicIR/Pretty.hs
+++ b/src/LogicIR/Pretty.hs
@@ -1,12 +1,5 @@
--- Very crude pretty printer for debugging, should not be used in production!
 module LogicIR.Pretty (prettyLExpr) where
 
-import Data.List
-import Data.Maybe
-import qualified Data.Map as M
-import Z3.Monad
-import Z3.Opts
-
 import LogicIR.Expr
 import LogicIR.Fold
 
diff --git a/src/Model.hs b/src/Model.hs
index 0757fb5..59d9658 100644
--- a/src/Model.hs
+++ b/src/Model.hs
@@ -16,10 +16,12 @@ import qualified Text.Parsec.Token as Tokens
 import LogicIR.ParserUtils
 
 -- | Response type.
-data Response = Equivalent | NotEquivalent Model | Undefined | Timeout
+data Response = Equivalent | NotEquivalent Model | ErrorResponse String | Undefined | Timeout
                 deriving (Eq)
 
 (<>) :: Response -> Response -> Response
+ErrorResponse e <> _ = ErrorResponse e
+_ <> ErrorResponse e = ErrorResponse e
 Equivalent <> r = r
 NotEquivalent s <> _ = NotEquivalent s
 Timeout <> _ = Timeout
@@ -27,9 +29,10 @@ Undefined <> _ = Undefined
 
 instance Show Response where
   show Equivalent = "Formulas are equivalent"
+  show (NotEquivalent model) = "Not equivalent: " ++ show model
+  show (ErrorResponse e) = "*** Error: " ++ show e
   show Undefined = "Oops... could not determine if formulas are equivalent"
   show Timeout = "Timeout occured"
-  show (NotEquivalent model) = "Not equivalent: " ++ show model
 
 
 -- | Model type.
diff --git a/src/Server.hs b/src/Server.hs
index 1b436aa..6fbb0e3 100644
--- a/src/Server.hs
+++ b/src/Server.hs
@@ -37,7 +37,6 @@ import Servant.Swagger.UI
 import API (Mode (..), ParseMode (..), compareSpec)
 import Model
 
-
 -- | Data types.
 data ApiReqBody = ApiReqBody
   { sourceA :: String
@@ -46,12 +45,13 @@ data ApiReqBody = ApiReqBody
 instance FromJSON ApiReqBody
 instance ToJSON ApiReqBody
 
-data ApiResponseType = Equiv | NotEquiv | Undef
+data ApiResponseType = Equiv | NotEquiv | Undef | ResponseErr
   deriving (Eq, Show, Generic)
 
 data ApiResponse = ApiResponse
   { responseType :: ApiResponseType
   , model        :: Maybe Model
+  , err          :: Maybe String
   }
   deriving (Eq, Show, Generic)
 instance ToJSON ApiResponseType
@@ -74,10 +74,13 @@ getCompareResponse ApiReqBody {sourceA = srcA, sourceB = srcB} = do
     resp <- liftIO $ compareSpec Release Raw (wrap srcA) (wrap srcB)
     return $ case resp of
                   Equivalent ->
-                    ApiResponse { responseType = Equiv, model = Nothing }
+                    ApiResponse { responseType = Equiv, model = Nothing, err = Nothing }
                   NotEquivalent m ->
-                    ApiResponse { responseType = NotEquiv, model = Just m }
-                  _ -> ApiResponse { responseType = Undef, model = Nothing }
+                    ApiResponse { responseType = NotEquiv, model = Just m, err = Nothing }
+                  ErrorResponse e ->
+                    ApiResponse { responseType = ResponseErr, model = Nothing, err = Just e }
+                  _ ->
+                    ApiResponse { responseType = Undef, model = Nothing, err = Nothing }
     where
       wrap s = ( "public class Main {" ++ s ++ "}"
                , last $ splitOn " " $ head $ splitOn "(" s
@@ -102,6 +105,7 @@ instance ToSample ApiResponse where
       [ ("a", ManyVal [RealVal 0.0, RealVal (-0.5)])
       , ("i", IntVal 10)
       ]
+    , err = Nothing
     }
 
 docsBS :: ByteString
@@ -127,12 +131,10 @@ serverSwagger = swaggerSchemaUIServer (toSwagger (Proxy :: Proxy CompareApi))
 
 -- | Server.
 runApi :: Int -> IO ()
-runApi port = do
-  let settings =
-        setPort port $
-        setBeforeMainLoop (hPutStrLn stderr ("listening on port " ++ show port))
-        defaultSettings
-  runSettings settings app
+runApi port = runSettings settings app
+  where settings = ( setPort port
+                   . setBeforeMainLoop (hPutStrLn stderr ("listening on port " ++ show port))
+                   ) defaultSettings
 
 type WholeApi = CompareApi :<|> SwagApi :<|> Raw
 
diff --git a/z3.log b/z3.log
deleted file mode 100644
index 61b1ef39c713caaf5c6f65cc7640a3dc2e6a1882..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 761813
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
z)%cNW6HxSt2;#*v{zJt-`5yrS1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7
z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N
z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+
z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly
zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7
z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N
z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+
z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly
zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7
z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N
z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+
z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly
zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7
z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N
z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+
z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly
zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7
z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N
z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+
z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly
zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7
z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N
z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+
z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly
zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs
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

-- 
GitLab