{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UndecidableInstances #-} module Overflow ( prop_sequential_overflow , prop_parallel_overflow , prop_nparallel_overflow ) where import Control.Concurrent (threadDelay) import Control.Concurrent.MVar import Control.Monad import Data.Int import GHC.Generics (Generic, Generic1) import Prelude import System.Random (randomRIO) import Test.QuickCheck import Test.QuickCheck.Monadic (monadicIO) import Test.StateMachine import Test.StateMachine.DotDrawing import qualified Test.StateMachine.Types.Rank2 as Rank2 ------------------------------------------------------------------------ -- The Model is a set of references of Int8. Command BackAndForth picks a random reference and adds a -- constant number (in an atomic way) and then substract the same number (in an atomic way). -- If there are enough threads (4 in this case) the result can overflow. data Command r = Create | Check (Reference (Opaque (MVar Int8)) r) | BackAndForth Int (Reference (Opaque (MVar Int8)) r) deriving stock (Eq, Generic1) deriving anyclass (Rank2.Functor, Rank2.Foldable, Rank2.Traversable, CommandNames) deriving stock instance Show (Command Symbolic) deriving stock instance Read (Command Symbolic) deriving stock instance Show (Command Concrete) data Response r = Created (Reference (Opaque (MVar Int8)) r) | IsNegative Bool | Unit deriving stock (Eq, Generic1) deriving anyclass (Rank2.Foldable) deriving stock instance Show (Response Symbolic) deriving stock instance Read (Response Symbolic) deriving stock instance Show (Response Concrete) newtype Model r = Model [(Reference (Opaque (MVar Int8)) r, Int8)] deriving stock (Generic, Show) getVars :: Model r -> [Reference (Opaque (MVar Int8)) r] getVars (Model ls) = fst <$> ls instance ToExpr (Model Symbolic) instance ToExpr (Model Concrete) initModel :: Model r initModel = Model [] transition :: Model r -> Command r -> Response r -> Model r transition m@(Model model) cmd resp = case (cmd, resp) of (Create, Created var) -> Model ((var, 0) : model) (Check _, IsNegative _) -> m (BackAndForth _ _, Unit) -> m _ -> error "impossible to transition!" precondition :: Model Symbolic -> Command Symbolic -> Logic precondition m cmd = case cmd of Create -> Top Check var -> var `member` getVars m BackAndForth _ var -> var `member` getVars m postcondition :: Model Concrete -> Command Concrete -> Response Concrete -> Logic postcondition _ cmd resp = case (cmd, resp) of (Create, Created _) -> Top (Check _, IsNegative bl) -> bl .== False (BackAndForth _ _, Unit) -> Top _ -> Bot semantics :: Command Concrete -> IO (Response Concrete) semantics cmd = case cmd of Create -> Created <$> (reference . Opaque <$> newMVar 0) Check var -> do val <- readMVar (opaque var) return $ IsNegative $ val < 0 BackAndForth n var -> do modifyMVar_ (opaque var) $ \x -> return $ x + fromIntegral n threadDelay =<< randomRIO (0, 5000) modifyMVar_ (opaque var) $ \x -> return $ x - fromIntegral n return Unit mock :: Model Symbolic -> Command Symbolic -> GenSym (Response Symbolic) mock _ cmd = case cmd of Create -> Created <$> genSym Check _var -> return $ IsNegative False BackAndForth _ _ -> return $ Unit generator :: Model Symbolic -> Maybe (Gen (Command Symbolic)) generator (Model []) = Just (return Create) generator m = Just $ frequency [ (1, return Create) , (3, genBackAndForth m) , (3, genCheck m) ] genCheck :: Model Symbolic -> Gen (Command Symbolic) genCheck m = Check <$> elements (getVars m) genBackAndForth :: Model Symbolic -> Gen (Command Symbolic) genBackAndForth m = do -- The upper limit here must have the property 2 * n < 128 <= 3 * n, so that -- there is a counter example for >= 4 threads. -- The lower limit only affects how fast a counterexample will be found. n <- choose (30,60) BackAndForth n <$> elements (getVars m) shrinker :: Model Symbolic -> Command Symbolic -> [Command Symbolic] shrinker _ (BackAndForth n var) = [ BackAndForth n' var | n' <- shrink n ] shrinker _ _ = [] sm :: StateMachine Model Command IO Response sm = StateMachine initModel transition precondition postcondition Nothing generator shrinker semantics mock noCleanup prop_sequential_overflow :: Property prop_sequential_overflow = forAllCommands sm Nothing $ \cmds -> monadicIO $ do (hist, _model, res) <- runCommands sm cmds prettyCommands sm hist (res === Ok) prop_parallel_overflow :: Property prop_parallel_overflow = forAllParallelCommands sm Nothing $ \cmds -> monadicIO $ prettyParallelCommandsWithOpts cmds opts =<< runParallelCommands sm cmds where opts = Just $ GraphOptions "overflow-test-output.png" Png prop_nparallel_overflow :: Int -> Property prop_nparallel_overflow np = forAllNParallelCommands sm np $ \cmds -> monadicIO $ prettyNParallelCommandsWithOpts cmds opts =<< runNParallelCommands sm cmds where opts = Just $ GraphOptions ("overflow-" ++ show np ++ "-test-output.png") Png