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