packages feed

crucible-debug-0.1.0: src/Lang/Crucible/Debug.hs

{-|
Copyright        : (c) Galois, Inc. 2025
Maintainer       : Langston Barrett <langston@galois.com>
-}

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}

module Lang.Crucible.Debug
  ( debugger
  , bareDebuggerExt
  , bareDebugger
  , Inps.defaultDebuggerInputs
  , Outps.defaultDebuggerOutputs
  , Arg.Arg(..)
  , AType.ArgTypeRepr(..)
  , type AType.Breakpoint
  , type AType.Exactly
  , type AType.Function
  , type AType.Int
  , type AType.Path
  , type AType.Text
  , Cmd.Command(Base, Ext)
  , Cmd.CommandExt(..)
  , Cmd.voidExts
  , Cmds.execStateSimState
  , Ctxt.EvalResult(..)
  , Ctxt.Context(..)
  , Ctxt.CommandImpl(..)
  , Ctxt.ExtImpl(..)
  , Ctxt.voidImpl
  , Resp.Response(Ok, UserError, XResponse)
  , Resp.UserError(NotApplicable)
  , Resp.NotApplicable(DoneSimulating, NotYetSimulating)
  , Resp.ResponseExt
  , type Rgx.Regex
  , type Rgx.Empty
  , type Rgx.Lit
  , type (Rgx.:|)
  , type Rgx.Then
  , type Rgx.Star
  , Rgx.Match(..)
  , Rgx.RegexRepr(..)
  , Trace.Trace
  , Trace.TraceEntry(..)
  , Trace.latest
  , IntrinsicPrinters(..)
  ) where

import Control.Applicative qualified as Applicative
import Control.Lens qualified as Lens
import Control.Monad qualified as Monad
import Data.IORef qualified as IORef
import Data.Maybe qualified as Maybe
import Data.Parameterized.Map qualified as MapF
import Data.Parameterized.Nonce qualified as Nonce
import Data.Parameterized.Some (Some(Some))
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Void (Void)
import Lang.Crucible.Backend qualified as C
import Lang.Crucible.Backend.Simple qualified as C
import Lang.Crucible.CFG.Extension qualified as C
import Lang.Crucible.Debug.Arg qualified as Arg
import Lang.Crucible.Debug.Arg.Type qualified as AType
import Lang.Crucible.Debug.Breakpoint qualified as Break
import Lang.Crucible.Debug.Command qualified as Cmd
import Lang.Crucible.Debug.Commands qualified as Cmds
import Lang.Crucible.Debug.Complete qualified as Complete
import Lang.Crucible.Debug.Complete (CompletionT)
import Lang.Crucible.Debug.Context (Context, DebuggerState)
import Lang.Crucible.Debug.Context qualified as Ctxt
import Lang.Crucible.Debug.Eval (EvalResult)
import Lang.Crucible.Debug.Eval qualified as Eval
import Lang.Crucible.Debug.Inputs (Inputs)
import Lang.Crucible.Debug.Inputs qualified as Inps
import Lang.Crucible.Debug.Outputs (Outputs)
import Lang.Crucible.Debug.Outputs qualified as Outps
import Lang.Crucible.Debug.Regex qualified as Rgx
import Lang.Crucible.Debug.Response qualified as Resp
import Lang.Crucible.Debug.Response (Response)
import Lang.Crucible.Debug.Statement (Statement)
import Lang.Crucible.Debug.Style qualified as Style
import Lang.Crucible.Debug.Style (StyleT)
import Lang.Crucible.Debug.Trace qualified as Trace
import Lang.Crucible.FunctionHandle qualified as C
import Lang.Crucible.Pretty (IntrinsicPrinters(..))
import Lang.Crucible.Simulator.CallFrame qualified as C
import Lang.Crucible.Simulator.EvalStmt qualified as C
import Lang.Crucible.Simulator.ExecutionTree qualified as C
import Lang.Crucible.Simulator qualified as C
import Lang.Crucible.Syntax.Concrete qualified as C
import Lang.Crucible.Types qualified as C
import Prettyprinter qualified as PP
import System.Exit qualified as Exit
import System.IO (stdout)
import What4.Expr qualified as W4
import What4.Interface qualified as W4

-- Useful for debugging
_printState :: C.ExecState p sym ext rtp -> Text
_printState =
  \case
    C.AbortState {} -> "AbortState"
    C.CallState {} -> "CallState"
    C.TailCallState {} -> "TailCallState"
    C.ReturnState {} -> "ReturnState"
    C.ResultState {} -> "ResultState"
    C.InitialState {} -> "InitialState"
    C.OverrideState {} -> "OverrideState"
    C.RunningState {} -> "RunningState"
    C.SymbolicBranchState {} -> "SymbolicBranchState"
    C.BranchMergeState {} -> "BranchMergeState"
    C.ControlTransferState {} -> "ControlTransferState"
    C.UnwindCallState {} -> "UnwindCallState"

stepState ::
  Context cExt p sym ext t ->
  C.ExecState p sym ext rtp ->
  IO DebuggerState
stepState ctx =
  \case
    C.AbortState {} ->
      if Ctxt.dbgStopOnAbort ctx
      then pure Ctxt.Stopped
      else pure initState
    C.CallState _retHandler frm _st -> do
      C.SomeHandle hdl <- pure (C.resolvedCallHandle frm)
      stepCall hdl
    C.TailCallState _vfv frm _st -> do
      C.SomeHandle hdl <- pure (C.resolvedCallHandle frm)
      stepCall hdl
    C.ReturnState _fnm _vfv _ret _st ->
      case initState of
        Ctxt.Running Ctxt.Finish -> pure Ctxt.Stopped
        _ -> pure initState
    C.RunningState (C.RunBlockEnd _) simState -> do
      let fr = simState Lens.^. C.stateCrucibleFrame
      C.CallFrame { C._frameCFG = cfg, C._frameBlockID = blkId } <- pure fr
      let ent = Trace.TraceEntry cfg blkId
      Trace.append (Ctxt.dbgTrace ctx) ent
      pure initState

    -- Nothing to do for these...
    C.InitialState {} -> pure initState
    C.OverrideState {} -> pure initState
    C.ResultState {} -> pure initState
    C.RunningState {} -> pure initState
    C.SymbolicBranchState {} -> pure initState

    -- These are mostly implementation details...
    C.BranchMergeState {} -> pure initState
    C.ControlTransferState {} -> pure initState
    C.UnwindCallState {} -> pure initState
  where
    initState = Ctxt.dbgState ctx

    stepCall :: C.FnHandle args ret ->  IO DebuggerState
    stepCall hdl = do
      let b = Break.BreakFunction (C.handleName hdl)
      if Set.member b (Ctxt.dbgBreakpoints ctx)
      then pure Ctxt.Stopped
      else pure initState

mergeResults ::
  C.ExecutionFeatureResult p sym ext ret ->
  C.ExecutionFeatureResult p sym ext ret ->
  C.ExecutionFeatureResult p sym ext ret
mergeResults old new =
  case old of
    C.ExecutionFeatureNoChange -> new
    _ ->
      case new of
        C.ExecutionFeatureNoChange -> old
        _ -> new

resultState ::
  C.ExecutionFeatureResult p sym ext ret ->
  Maybe (C.ExecState p sym ext ret)
resultState =
  \case
    C.ExecutionFeatureNoChange -> Nothing
    C.ExecutionFeatureModifiedState s -> Just s
    C.ExecutionFeatureNewState s -> Just s

stopped ::
  (sym ~ W4.ExprBuilder s st fs) =>
  C.IsSymInterface sym =>
  C.IsSyntaxExtension ext =>
  (?parserHooks :: C.ParserHooks ext) =>
  PP.Pretty cExt =>
  W4.IsExpr (W4.SymExpr sym) =>
  Context cExt p sym ext t ->
  C.ExecState p sym ext (C.RegEntry sym t) ->
  IO (EvalResult cExt p sym ext t)
stopped ctx0 execState0 = go ctx0 execState0 C.ExecutionFeatureNoChange
  where
    go c0 s0 r = do
      let cEnv = Ctxt.toCompletionEnv c0 s0
      let sEnv = Ctxt.toStyleEnv c0 s0
      stmt <- Style.runStyleM sEnv (Complete.runCompletionM cEnv (Inps.recv (Ctxt.dbgInputs c0)))
      result0 <- Eval.eval c0 s0 stmt
      let featResult = mergeResults r (Eval.evalFeatureResult result0)
      let result = result0 { Eval.evalFeatureResult = featResult }
      let s = Maybe.fromMaybe s0 (resultState featResult)
      let c = Eval.evalCtx result
      Outps.send (Ctxt.dbgOutputs c) (Eval.evalResp result)
      case Ctxt.dbgState c of
        Ctxt.Quit -> pure result
        Ctxt.Running {} -> pure result
        Ctxt.Stopped -> go c s featResult

dispatch ::
  (sym ~ W4.ExprBuilder s st fs) =>
  C.IsSymInterface sym =>
  C.IsSyntaxExtension ext =>
  W4.IsExpr (W4.SymExpr sym) =>
  (?parserHooks :: C.ParserHooks ext) =>
  PP.Pretty cExt =>
  Context cExt p sym ext t ->
  C.ExecState p sym ext (C.RegEntry sym t) ->
  IO (Context cExt p sym ext t, C.ExecutionFeatureResult p sym ext (C.RegEntry sym t))
dispatch ctx0 execState =
  case Ctxt.dbgState ctx0 of
    Ctxt.Quit ->
      pure (ctx0, C.ExecutionFeatureNoChange)
    Ctxt.Running {} | C.ResultState {} <- execState -> do
      let ctx = ctx0 { Ctxt.dbgState = Ctxt.Stopped }
      dispatch ctx execState
    Ctxt.Running (Ctxt.Step i) | i <= 1 -> do
      let ctx = ctx0 { Ctxt.dbgState = Ctxt.Stopped }
      dispatch ctx execState
    Ctxt.Running (Ctxt.Step i) -> do
      let ctx = ctx0 { Ctxt.dbgState = Ctxt.Running (Ctxt.Step (i - 1)) }
      state <- stepState ctx execState
      let ctx' = ctx0 { Ctxt.dbgState = state }
      pure (ctx', C.ExecutionFeatureNoChange)
    Ctxt.Running {} -> do
      state <- stepState ctx0 execState
      let ctx = ctx0 { Ctxt.dbgState = state }
      pure (ctx, C.ExecutionFeatureNoChange)
    Ctxt.Stopped -> do
      result <- stopped ctx0 execState
      pure (Eval.evalCtx result, Eval.evalFeatureResult result)

debugger ::
  (sym ~ W4.ExprBuilder s st fs) =>
  C.IsSymInterface sym =>
  C.IsSyntaxExtension ext =>
  W4.IsExpr (W4.SymExpr sym) =>
  (?parserHooks :: C.ParserHooks ext) =>
  PP.Pretty cExt =>
  Cmd.CommandExt cExt ->
  Ctxt.ExtImpl cExt p sym ext t ->
  IntrinsicPrinters sym ->
  Inputs (CompletionT cExt (StyleT cExt IO)) (Statement cExt) ->
  Outputs IO (Response cExt) ->
  C.TypeRepr t ->
  IO (C.ExecutionFeature p sym ext (C.RegEntry sym t))
debugger cExt impl iFns ins outs rTy = do
  ctxRef <- IORef.newIORef =<< Ctxt.initCtx cExt impl iFns ins outs rTy
  pure $
    C.ExecutionFeature $ \execState -> do
      ctx0 <- IORef.readIORef ctxRef
      (ctx, featResult) <- dispatch ctx0 execState
      IORef.writeIORef ctxRef ctx
      pure featResult

-- | Like 'bareDebugger', but with a syntax extension
bareDebuggerExt ::
  C.IsSyntaxExtension ext =>
  (?parserHooks :: C.ParserHooks ext) =>
  PP.Pretty cExt =>
  Cmd.CommandExt cExt ->
  (forall p sym t. C.IsSymInterface sym => Ctxt.ExtImpl cExt p sym ext t) ->
  (forall p sym. C.IsSymInterface sym => C.ExtensionImpl p sym ext) ->
  Inputs (CompletionT cExt (StyleT cExt IO)) (Statement cExt) ->
  Outputs IO (Response cExt) ->
  (Text -> IO ()) ->
  IO ()
bareDebuggerExt cExts cEval extImpl inps outps logger = do
  Some nonceGen <- Nonce.newIONonceGenerator
  ha <- C.newHandleAllocator
  sym <- W4.newExprBuilder W4.FloatIEEERepr W4.EmptyExprBuilderState nonceGen
  bak <- C.newSimpleBackend sym
  let retType = C.UnitRepr
  let fns = C.FnBindings C.emptyHandleMap
  let simCtx = C.initSimContext bak C.emptyIntrinsicTypes ha stdout fns extImpl ()
  let ov = C.runOverrideSim retType $ return ()
  let simSt  = C.InitialState simCtx C.emptyGlobals C.defaultAbortHandler retType ov
  dbgr <- do
    let iFns = IntrinsicPrinters MapF.empty
    debugger cExts cEval iFns inps outps retType
  Monad.void $ C.executeCrucible [dbgr] simSt
  C.getProofObligations bak >>= \case
    Nothing -> pure ()
    Just {} -> do
      logger "There were unhandled proof obligations! Try `prove` and `clear`."
      Exit.exitFailure

-- | Start a debugger instance in an empty Crucible program.
--
-- The Crucible program simply returns the unit value. The idea is to use the
-- @Load@ and @Call@ commands to actually run some Crucible CFG(s).
--
-- Proves obligations using Z3.
bareDebugger ::
  Inputs (CompletionT Void (StyleT Void IO)) (Statement Void) ->
  Outputs IO (Response Void) ->
  (Text -> IO ()) ->
  IO ()
bareDebugger inps outps logger =
  let ?parserHooks = C.ParserHooks Applicative.empty Applicative.empty in
  bareDebuggerExt
    Cmd.voidExts
    Ctxt.voidImpl
    C.emptyExtensionImpl
    inps
    outps
    logger