1-- SPDX-FileCopyrightText: 2025-2026 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only45module Language.QBE.Simulator.Concolic.State6 ( Env (..),7 mkEnv,8 run,9 runPath,10 makeConcolic,11 ErrorState (..),12 ErrorPath (..),13 SimState (..),14 )15where1617import Control.Exception (Exception, throwIO, try)18import Control.Monad.Error.Class (MonadError, catchError, throwError)19import Control.Monad.IO.Class (MonadIO, liftIO)20import Control.Monad.State.Strict21 ( MonadState,22 StateT (StateT),23 evalStateT,24 get,25 gets,26 modify,27 runStateT,28 )29import Data.Map qualified as Map30import Data.Word (Word8)31import Language.QBE (Program)32import Language.QBE.Backend.Store qualified as ST33import Language.QBE.Backend.Tracer qualified as T34import Language.QBE.Simulator.Concolic.Expression qualified as CE35import Language.QBE.Simulator.Default.Expression qualified as DE36import Language.QBE.Simulator.Default.Funcs (lookupSimFunc)37import Language.QBE.Simulator.Default.State qualified as DS38import Language.QBE.Simulator.Error (EvalError (FuncArgsMismatch, TypingError))39import Language.QBE.Simulator.Expression qualified as E40import Language.QBE.Simulator.Memory qualified as MEM41import Language.QBE.Simulator.State42import Language.QBE.Simulator.Symbolic.Expression qualified as SE43import Language.QBE.Types qualified as QBE44import System.Random (initStdGen, mkStdGen)4546data Env47 = Env48 { envBase :: DS.Env (CE.Concolic DE.RegVal) (CE.Concolic Word8),49 envTracer :: T.ExecTrace,50 envStore :: ST.Store51 }5253mkEnv ::54 Program ->55 MEM.Address ->56 MEM.Size ->57 Maybe Int ->58 IO Env59mkEnv prog memStart memSize maySeed = do60 initEnv <- DS.mkEnv prog memStart memSize61 randGen <-62 case maySeed of63 Just sd -> pure $ mkStdGen sd64 Nothing -> initStdGen65 pure $ Env initEnv T.newExecTrace (ST.empty randGen)6667liftState ::68 (DS.SimState (CE.Concolic DE.RegVal) (CE.Concolic Word8)) a ->69 SimState a70liftState (DS.SimState toLift) = do71 defEnv <- gets envBase7273 -- XXX: Since 'toLift' is run in the DS.SimState monad, it would74 -- use its 'throwError' implementation here. We want to use the75 -- implementation of our 'SimState' though, hence we need to handle76 -- IO exceptions here.77 result <- liftIO $ try (runStateT toLift defEnv)78 case result of79 Left (e :: EvalError) -> throwError e80 Right (a, s) -> do81 modify (\ps -> ps {envBase = s})82 pure a8384makeConcolic :: String -> QBE.ExtType -> SimState (CE.Concolic DE.RegVal)85makeConcolic name ty = do86 st <- gets envStore87 let (ns, cv) = ST.getConcolic st name ty88 modify (\e -> e {envStore = ns})89 pure cv9091modifyTracer :: (MonadState Env m) => (T.ExecTrace -> T.ExecTrace) -> m ()92modifyTracer f =93 modify (\s@Env {envTracer = t} -> s {envTracer = f t})9495makeSymbolicArray ::96 QBE.GlobalIdent ->97 [CE.Concolic DE.RegVal] ->98 SimState (Maybe (CE.Concolic DE.RegVal))99makeSymbolicArray _ [arrayPtr, numElem, elemSize, namePtr] = do100 name <- E.toString <$> (toAddress namePtr >>= readNullArray)101 vlty <- case E.toWord64 elemSize of102 1 -> pure QBE.Byte103 2 -> pure QBE.HalfWord104 4 -> pure (QBE.Base QBE.Word)105 8 -> pure (QBE.Base QBE.Long)106 _ -> throwError TypingError107108 values <-109 mapM110 (\n -> makeConcolic (name ++ show n) vlty)111 [1 .. E.toWord64 numElem]112113 arrayAddr <- toAddress arrayPtr114 liftState (DS.SimState $ DS.storeValues arrayAddr values) >> pure Nothing115makeSymbolicArray ident _ = throwError $ FuncArgsMismatch ident116117findSimFunc :: QBE.GlobalIdent -> Maybe ([CE.Concolic DE.RegVal] -> SimState (Maybe (CE.Concolic DE.RegVal)))118findSimFunc i@(QBE.GlobalIdent "quebex_make_symbolic") = Just (makeSymbolicArray i)119findSimFunc ident = lookupSimFunc ident120121------------------------------------------------------------------------122123-- | State of the concolic executor with which an error was triggered124-- in the application code, which can be reproduced using this state.125data ErrorState126 = ErrorState127 { errTracer :: T.ExecTrace,128 errStore :: ST.Store129 }130131-- | Exception thrown upon encountered an 'ErrorState'.132data ErrorPath133 = ErrorPath134 { pathInput :: ErrorState,135 pathError :: EvalError136 }137138instance Exception ErrorPath139140instance Show ErrorPath where141 show (ErrorPath _ err) = show err142143------------------------------------------------------------------------144145newtype SimState a = SimState {unSimState :: StateT Env IO a}146 deriving (Functor, Applicative, Monad, MonadIO)147148deriving instance MonadState Env SimState149150-- Implements 'MonadError' in 'SimState' via 'IOException's. On throw,151-- it also returns the relevant executor state by encapsulting it in152-- an 'ErrorPath'.153--154-- See also: The instance for 'DS.SimState'.155instance MonadError EvalError SimState where156 throwError err = do157 Env {envTracer = t, envStore = s} <- get158 liftIO $ throwIO (ErrorPath (ErrorState t s) err)159160 catchError (SimState st) handler =161 SimState $ DS.unliftCatch st (unSimState . handler)162163------------------------------------------------------------------------164165instance Simulator SimState (CE.Concolic DE.RegVal) where166 isTrue value = do167 let condResult = E.toWord64 (CE.concrete value) /= 0168 case CE.symbolic value of169 Nothing -> pure condResult170 Just sexpr -> do171 -- Track the taken branch in the tracer.172 let branch = T.newBranch sexpr173 modifyTracer (\t -> T.appendBranch t condResult branch)174175 pure condResult176177 -- Implements address concretization as a memory model.178 toAddress CE.Concolic {CE.concrete = cv, CE.symbolic = svMaybe} =179 case svMaybe of180 Just sv ->181 case sv `E.eq` SE.fromReg cv of182 Just c -> do183 modifyTracer (`T.appendCons` c)184 pure $ E.toWord64 cv185 Nothing -> throwError TypingError186 Nothing -> pure $ E.toWord64 cv187188 findFunc ident = do189 funcs <- gets (DS.envFuncs . envBase)190 pure $ case Map.lookup ident funcs of191 Just x -> Just $ SFuncDef x192 Nothing -> SSimFunc <$> findSimFunc ident193 findFuncByAddr addr = do194 fptrs <- gets (DS.envFuncAddrs . envBase)195 case Map.lookup addr fptrs of196 Just fn -> findFunc fn197 Nothing -> pure Nothing198199 lookupSymbol = liftState . lookupSymbol200 activeFrame = liftState activeFrame201 pushStackFrame = liftState . pushStackFrame202 popStackFrame = liftState popStackFrame203 getSP = liftState getSP204 setSP = liftState . setSP205206 writeMemory a t v = liftState (writeMemory a t v)207 readMemory t a = liftState (readMemory t a)208209------------------------------------------------------------------------210211runPath :: SimState a -> SimState (T.ExecTrace, ST.Store)212runPath state = do213 _ <- state214 t <- gets envTracer215 s <- gets envStore216 pure (t, s)217218run :: Env -> SimState a -> IO (T.ExecTrace, ST.Store)219run env state = evalStateT (unSimState $ runPath state) env