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.ABI.C (readString)35import Language.QBE.Simulator.Concolic.Expression qualified as CE36import Language.QBE.Simulator.Default.Expression qualified as DE37import Language.QBE.Simulator.Default.Funcs (lookupSimFunc)38import Language.QBE.Simulator.Default.State qualified as DS39import Language.QBE.Simulator.Error (EvalError (FuncArgsMismatch, TerminatePath, TypingError))40import Language.QBE.Simulator.Expression qualified as E41import Language.QBE.Simulator.Memory qualified as MEM42import Language.QBE.Simulator.State43import Language.QBE.Simulator.Symbolic.Expression qualified as SE44import Language.QBE.Types qualified as QBE45import System.Random (initStdGen, mkStdGen)4647data Env48 = Env49 { envBase :: DS.Env (CE.Concolic DE.RegVal) (CE.Concolic Word8),50 envTracer :: T.ExecTrace,51 envStore :: ST.Store52 }5354mkEnv ::55 Program ->56 MEM.Address ->57 MEM.Size ->58 Bool ->59 Maybe Int ->60 IO Env61mkEnv prog memStart memSize initMem maySeed = do62 initEnv <- DS.mkEnv prog memStart memSize initMem63 randGen <-64 case maySeed of65 Just sd -> pure $ mkStdGen sd66 Nothing -> initStdGen67 pure $ Env initEnv T.newExecTrace (ST.empty randGen)6869liftState ::70 (DS.SimState (CE.Concolic DE.RegVal) (CE.Concolic Word8)) a ->71 SimState a72liftState (DS.SimState toLift) = do73 defEnv <- gets envBase7475 -- XXX: Since 'toLift' is run in the DS.SimState monad, it would76 -- use its 'throwError' implementation here. We want to use the77 -- implementation of our t'SimState' though, hence we need to handle78 -- IO exceptions here.79 result <- liftIO $ try (runStateT toLift defEnv)80 case result of81 Left (e :: EvalError) -> throwError e82 Right (a, s) -> do83 modify (\ps -> ps {envBase = s})84 pure a8586makeConcolic :: String -> QBE.ExtType -> SimState (CE.Concolic DE.RegVal)87makeConcolic name ty = do88 st <- gets envStore89 let (ns, cv) = ST.getConcolic st name ty90 modify (\e -> e {envStore = ns})91 pure cv9293modifyTracer :: (MonadState Env m) => (T.ExecTrace -> T.ExecTrace) -> m ()94modifyTracer f =95 modify (\s@Env {envTracer = t} -> s {envTracer = f t})9697makeSymbolicArray ::98 QBE.GlobalIdent ->99 [CE.Concolic DE.RegVal] ->100 SimState (Maybe (CE.Concolic DE.RegVal))101makeSymbolicArray _ [arrayPtr, numElem, elemSize, namePtr] = do102 name <- E.toString <$> (toAddress namePtr >>= readString)103 vlty <- case E.toWord64 elemSize of104 1 -> pure QBE.Byte105 2 -> pure QBE.HalfWord106 4 -> pure (QBE.Base QBE.Word)107 8 -> pure (QBE.Base QBE.Long)108 _ -> throwError TypingError109110 values <-111 mapM112 (\n -> makeConcolic (name ++ "_e" ++ show n) vlty)113 [1 .. E.toWord64 numElem]114115 arrayAddr <- toAddress arrayPtr116 liftState (DS.SimState $ DS.storeValues arrayAddr values) >> pure Nothing117makeSymbolicArray ident _ = throwError $ FuncArgsMismatch ident118119exit ::120 QBE.GlobalIdent ->121 [CE.Concolic DE.RegVal] ->122 SimState (Maybe (CE.Concolic DE.RegVal))123exit _ [_code] = throwError TerminatePath124exit ident _ = throwError $ FuncArgsMismatch ident125126findSimFunc :: QBE.GlobalIdent -> Maybe ([CE.Concolic DE.RegVal] -> SimState (Maybe (CE.Concolic DE.RegVal)))127findSimFunc i@(QBE.GlobalIdent "qute_make_symbolic") = Just (makeSymbolicArray i)128findSimFunc i@(QBE.GlobalIdent "qute_exit") = Just (exit i)129findSimFunc ident = lookupSimFunc ident130131------------------------------------------------------------------------132133-- | State of the concolic executor with which an error was triggered134-- in the application code, which can be reproduced using this state.135data ErrorState136 = ErrorState137 { errTracer :: T.ExecTrace,138 errStore :: ST.Store139 }140141-- | Exception thrown upon encountered an t'ErrorState'.142data ErrorPath143 = ErrorPath144 { pathInput :: ErrorState,145 pathError :: EvalError146 }147148instance Exception ErrorPath149150instance Show ErrorPath where151 show (ErrorPath _ err) = show err152153------------------------------------------------------------------------154155newtype SimState a = SimState {unSimState :: StateT Env IO a}156 deriving (Functor, Applicative, Monad, MonadIO)157158deriving instance MonadState Env SimState159160-- Implements 'MonadError' in t'SimState' via 'IOException's. On throw,161-- it also returns the relevant executor state by encapsulting it in162-- an 'ErrorPath'.163--164-- See also: The instance for 'DS.SimState'.165instance MonadError EvalError SimState where166 throwError err = do167 Env {envTracer = t, envStore = s} <- get168 liftIO $ throwIO (ErrorPath (ErrorState t s) err)169170 catchError (SimState st) handler =171 SimState $ DS.unliftCatch st (unSimState . handler)172173------------------------------------------------------------------------174175instance Simulator SimState (CE.Concolic DE.RegVal) where176 isTrue value = do177 let condResult = E.toWord64 (CE.concrete value) /= 0178 case CE.symbolic value of179 Nothing -> pure condResult180 Just sexpr -> do181 -- Track the taken branch in the tracer.182 let branch = T.newBranch sexpr183 modifyTracer (\t -> T.appendBranch t condResult branch)184185 pure condResult186187 -- Implements address concretization as a memory model.188 toAddress CE.Concolic {CE.concrete = cv, CE.symbolic = svMaybe} =189 case svMaybe of190 Just sv ->191 case sv `E.eq` SE.fromReg cv of192 Just c -> do193 modifyTracer (`T.appendCons` c)194 pure $ E.toWord64 cv195 Nothing -> throwError TypingError196 Nothing -> pure $ E.toWord64 cv197198 findFunc ident = do199 funcs <- gets (DS.envFuncs . envBase)200 pure $ case Map.lookup ident funcs of201 Just x -> Just $ SFuncDef x202 Nothing -> SSimFunc <$> findSimFunc ident203 findFuncByAddr addr = do204 fptrs <- gets (DS.envFuncAddrs . envBase)205 case Map.lookup addr fptrs of206 Just fn -> findFunc fn207 Nothing -> pure Nothing208209 lookupSymbol = liftState . lookupSymbol210 activeFrame = liftState activeFrame211 pushStackFrame = liftState . pushStackFrame212 popStackFrame = liftState popStackFrame213 getSP = liftState getSP214 setSP = liftState . setSP215216 writeMemory a t v = liftState (writeMemory a t v)217 readMemory t a = liftState (readMemory t a)218219------------------------------------------------------------------------220221runPath :: SimState a -> SimState (T.ExecTrace, ST.Store)222runPath state = do223 _ <- state224 t <- gets envTracer225 s <- gets envStore226 pure (t, s)227228run :: Env -> SimState a -> IO (T.ExecTrace, ST.Store)229run env state = evalStateT (unSimState $ runPath state) env