packages feed

ghc-debugger 0.1.0.0 → 0.2.0.0

raw patch · 16 files changed

+970/−692 lines, 16 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- GHC.Debugger: clearBreakpoints :: Maybe FilePath -> Debugger ()
- GHC.Debugger: debugExecution :: EntryPoint -> [String] -> Debugger EvalResult
- GHC.Debugger: defaultDepth :: Int
- GHC.Debugger: display :: Outputable a => a -> Debugger String
- GHC.Debugger: doContinue :: Debugger EvalResult
- GHC.Debugger: doEval :: String -> Debugger EvalResult
- GHC.Debugger: doLocalStep :: Debugger EvalResult
- GHC.Debugger: doSingleStep :: Debugger EvalResult
- GHC.Debugger: getBreakpointsAt :: ModSummary -> Int -> Maybe Int -> Debugger (Maybe (BreakIndex, RealSrcSpan))
- GHC.Debugger: getScopes :: Debugger [ScopeInfo]
- GHC.Debugger: getStacktrace :: Debugger [StackFrame]
- GHC.Debugger: getTopEnv :: Module -> Debugger TypeEnv
- GHC.Debugger: getTopImported :: Module -> Debugger GlobalRdrEnv
- GHC.Debugger: getVariables :: VariableReference -> Debugger (Either VarInfo [VarInfo])
- GHC.Debugger: handleExecResult :: ExecResult -> Debugger EvalResult
- GHC.Debugger: inspectName :: Name -> Debugger (Maybe VarInfo)
- GHC.Debugger: isBoringTy :: Type -> Bool
- GHC.Debugger: leaveSuspendedState :: Debugger ()
- GHC.Debugger: realSrcSpanToSourceSpan :: RealSrcSpan -> SourceSpan
- GHC.Debugger: setBreakpoint :: Breakpoint -> BreakpointStatus -> Debugger BreakFound
- GHC.Debugger: termToVarInfo :: Name -> Term -> Debugger VarInfo
- GHC.Debugger: tyThingToVarInfo :: Int -> TyThing -> Debugger VarInfo
- GHC.Debugger.Interface.Messages: [varFields] :: VarInfo -> VarFields
- GHC.Debugger.Monad: insertVarReference :: Int -> Name -> Term -> Debugger ()
+ GHC.Debugger.Breakpoint: clearBreakpoints :: Maybe FilePath -> Debugger ()
+ GHC.Debugger.Breakpoint: getBreakpointsAt :: ModSummary -> Int -> Maybe Int -> Debugger (Maybe (BreakIndex, RealSrcSpan))
+ GHC.Debugger.Breakpoint: setBreakpoint :: Breakpoint -> BreakpointStatus -> Debugger BreakFound
+ GHC.Debugger.Evaluation: debugExecution :: EntryPoint -> [String] -> Debugger EvalResult
+ GHC.Debugger.Evaluation: doContinue :: Debugger EvalResult
+ GHC.Debugger.Evaluation: doEval :: String -> Debugger EvalResult
+ GHC.Debugger.Evaluation: doLocalStep :: Debugger EvalResult
+ GHC.Debugger.Evaluation: doSingleStep :: Debugger EvalResult
+ GHC.Debugger.Evaluation: handleExecResult :: ExecResult -> Debugger EvalResult
+ GHC.Debugger.Evaluation: inspectName :: Name -> Debugger (Maybe VarInfo)
+ GHC.Debugger.Monad: [termCache] :: DebuggerState -> IORef TermCache
+ GHC.Debugger.Monad: defaultDepth :: Int
+ GHC.Debugger.Monad: getVarReference :: TermKey -> Debugger Int
+ GHC.Debugger.Monad: leaveSuspendedState :: Debugger ()
+ GHC.Debugger.Runtime: isBoringTy :: Type -> Bool
+ GHC.Debugger.Runtime: obtainTerm :: TermKey -> Debugger Term
+ GHC.Debugger.Runtime.Term.Cache: insertTermCache :: TermKey -> Term -> TermCache -> TermCache
+ GHC.Debugger.Runtime.Term.Cache: insertTermKeyMap :: TermKey -> a -> TermKeyMap a -> TermKeyMap a
+ GHC.Debugger.Runtime.Term.Cache: lookupTermCache :: TermKey -> TermCache -> Maybe Term
+ GHC.Debugger.Runtime.Term.Cache: lookupTermKeyMap :: TermKey -> TermKeyMap a -> Maybe a
+ GHC.Debugger.Runtime.Term.Cache: type TermCache = TermKeyMap Term
+ GHC.Debugger.Runtime.Term.Cache: type TermKeyMap a = IdEnv Map [PathFragment] a
+ GHC.Debugger.Runtime.Term.Key: LabeledField :: Name -> PathFragment
+ GHC.Debugger.Runtime.Term.Key: PositionalIndex :: Int -> PathFragment
+ GHC.Debugger.Runtime.Term.Key: [FromId] :: Id -> TermKey
+ GHC.Debugger.Runtime.Term.Key: [FromPath] :: TermKey -> PathFragment -> TermKey
+ GHC.Debugger.Runtime.Term.Key: data PathFragment
+ GHC.Debugger.Runtime.Term.Key: data TermKey
+ GHC.Debugger.Runtime.Term.Key: instance GHC.Internal.Classes.Eq GHC.Debugger.Runtime.Term.Key.PathFragment
+ GHC.Debugger.Runtime.Term.Key: instance GHC.Internal.Classes.Ord GHC.Debugger.Runtime.Term.Key.PathFragment
+ GHC.Debugger.Runtime.Term.Key: instance GHC.Utils.Outputable.Outputable GHC.Debugger.Runtime.Term.Key.PathFragment
+ GHC.Debugger.Runtime.Term.Key: instance GHC.Utils.Outputable.Outputable GHC.Debugger.Runtime.Term.Key.TermKey
+ GHC.Debugger.Runtime.Term.Key: unconsTermKey :: TermKey -> (Id, [PathFragment])
+ GHC.Debugger.Stopped: getScopes :: Debugger [ScopeInfo]
+ GHC.Debugger.Stopped: getStacktrace :: Debugger [StackFrame]
+ GHC.Debugger.Stopped: getTopEnv :: Module -> Debugger TypeEnv
+ GHC.Debugger.Stopped: getTopImported :: Module -> Debugger GlobalRdrEnv
+ GHC.Debugger.Stopped: getVariables :: VariableReference -> Debugger (Either VarInfo [VarInfo])
+ GHC.Debugger.Stopped.Variables: termToVarInfo :: TermKey -> Term -> Debugger VarInfo
+ GHC.Debugger.Stopped.Variables: termVarFields :: TermKey -> Term -> Debugger VarFields
+ GHC.Debugger.Stopped.Variables: tyThingToVarInfo :: TyThing -> Debugger VarInfo
+ GHC.Debugger.Utils: display :: Outputable a => a -> Debugger String
+ GHC.Debugger.Utils: realSrcSpanToSourceSpan :: RealSrcSpan -> SourceSpan
- GHC.Debugger.Interface.Messages: VarInfo :: String -> String -> String -> Bool -> VariableReference -> VarFields -> VarInfo
+ GHC.Debugger.Interface.Messages: VarInfo :: String -> String -> String -> Bool -> VariableReference -> VarInfo
- GHC.Debugger.Monad: DebuggerState :: IORef (ModuleEnv (IntMap (BreakpointStatus, BreakpointKind))) -> IORef (IntMap (Name, Term)) -> IORef Int -> DebuggerState
+ GHC.Debugger.Monad: DebuggerState :: IORef (ModuleEnv (IntMap (BreakpointStatus, BreakpointKind))) -> IORef (IntMap TermKey, TermKeyMap Int) -> IORef TermCache -> IORef Int -> DebuggerState
- GHC.Debugger.Monad: [varReferences] :: DebuggerState -> IORef (IntMap (Name, Term))
+ GHC.Debugger.Monad: [varReferences] :: DebuggerState -> IORef (IntMap TermKey, TermKeyMap Int)
- GHC.Debugger.Monad: lookupVarByReference :: Int -> Debugger (Maybe (Name, Term))
+ GHC.Debugger.Monad: lookupVarByReference :: Int -> Debugger (Maybe TermKey)

Files

README.md view
@@ -30,7 +30,7 @@ ``` ghcup config add-release-channel https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml ghcup install ghc latest-nightly -PATH=$(dirname $(ghcup whereis ghc latest-nightly)):$PATH cabal install exe:ghc-debug-adapter --enable-executable-dynamic --allow-newer=ghc-bignum,containers,time,ghc+PATH=$(dirname $(ghcup whereis ghc latest-nightly)):$PATH cabal install ghc-debugger:ghc-debug-adapter --enable-executable-dynamic --allow-newer=ghc-bignum,containers,time,ghc ```  To run the debugger, the same nightly version of GHC needs to be in PATH. Make
ghc-debug-adapter/Development/Debug/Adapter/Init.hs view
@@ -72,10 +72,10 @@   -- GHC is found in PATH (by hie-bios as well).   actualVersion <- liftIO $ P.readProcess "ghc" ["--numeric-version"] []   -- Compare the GLASGOW_HASKELL version (e.g. 913) with the actualVersion (e.g. 9.13.1):-  when (not $ show __GLASGOW_HASKELL__ `L.isPrefixOf` (filter (/= '.') actualVersion)) $ do+  when (not $ show ( __GLASGOW_HASKELL__ :: Int ) `L.isPrefixOf` (filter (/= '.') actualVersion)) $ do     exitWithMsg $ "Aborting...! The GHC version must be the same which " ++                     "ghc-debug-adapter was compiled against (" ++-                      show __GLASGOW_HASKELL__ +++                      show ( __GLASGOW_HASKELL__ :: Int )++                         "). Instead, got " ++ (init{-drops \n-} actualVersion) ++ "."    Output.console $ T.pack "Discovering session flags with hie-bios..."
ghc-debug-adapter/Development/Debug/Adapter/Stopped.hs view
@@ -146,12 +146,8 @@     , variableType = Just $ T.pack varType     , variableEvaluateName = Just $ T.pack varName     , variableVariablesReference = fromEnum varRef-    , variableNamedVariables = case varFields of-        LabeledFields xs -> Just $ length xs-        _                -> Nothing-    , variableIndexedVariables = case varFields of-        IndexedFields xs -> Just $ length xs-        _                -> Nothing+    , variableNamedVariables = Nothing+    , variableIndexedVariables = Nothing     , variablePresentationHint = Just defaultVariablePresentationHint         { variablePresentationHintLazy = Just isThunk         }
ghc-debug-adapter/Main.hs view
@@ -2,20 +2,16 @@ module Main where  import System.Environment-import System.Exit import Data.Maybe import Text.Read  import DAP -import GHC.Debugger.Interface.Messages hiding (Command, Response)- import Development.Debug.Adapter.Init import Development.Debug.Adapter.Breakpoints import Development.Debug.Adapter.Stepping import Development.Debug.Adapter.Stopped import Development.Debug.Adapter.Evaluation-import Development.Debug.Adapter.Interface import Development.Debug.Adapter.Exit import Development.Debug.Adapter.Handles import Development.Debug.Adapter
ghc-debugger.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               ghc-debugger-version:            0.1.0.0+version:            0.2.0.0 synopsis:     A step-through machine-interface debugger for GHC Haskell @@ -42,6 +42,16 @@ library     import:           warnings     exposed-modules:  GHC.Debugger,+                      GHC.Debugger.Evaluation,+                      GHC.Debugger.Breakpoint,+                      GHC.Debugger.Stopped,+                      GHC.Debugger.Stopped.Variables,+                      GHC.Debugger.Utils,+                      GHC.Debugger.Runtime,++                      GHC.Debugger.Runtime.Term.Key,+                      GHC.Debugger.Runtime.Term.Cache,+                       GHC.Debugger.Monad,                       GHC.Debugger.Interface.Messages     -- other-modules:
ghc-debugger/GHC/Debugger.hs view
@@ -3,54 +3,18 @@    TypeApplications, ScopedTypeVariables, BangPatterns #-} module GHC.Debugger where -import Prelude hiding (exp, span) import System.Exit-import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Catch-import Data.Bits (xor) -import GHC-import GHC.Types.Unique.FM-import GHC.Types.Name.Reader #if MIN_VERSION_ghc(9,13,20250417) import GHC.Types.Name.Occurrence (sizeOccEnv) #endif-import GHC.Unit.Home.ModInfo-import GHC.Unit.Module.ModDetails-import GHC.Types.FieldLabel-import GHC.Types.TypeEnv-import GHC.Data.Maybe (expectJust)-import GHC.Builtin.Names (gHC_INTERNAL_GHCI_HELPERS, mkUnboundName)-import GHC.Data.FastString-import GHC.Utils.Error (logOutput)-import GHC.Driver.DynFlags as GHC-import GHC.Driver.Env as GHC-import GHC.Driver.Monad-import GHC.Driver.Ppr as GHC-import GHC.Runtime.Debugger.Breakpoints as GHC-import GHC.Runtime.Eval.Types as GHC-import GHC.Runtime.Eval-import GHC.Core.DataCon-import GHC.Types.Breakpoint-import GHC.Types.Id as GHC-import GHC.Types.Name.Occurrence (mkVarOcc, mkVarOccFS)-import GHC.Types.Name.Reader as RdrName (mkOrig, globalRdrEnvElts, greName)-import GHC.Types.SrcLoc-import GHC.Tc.Utils.TcType-import GHC.Unit.Module.Env as GHC-import GHC.Utils.Outputable as GHC-import GHC.Utils.Misc (zipEqual)-import qualified GHC.Runtime.Debugger as GHCD-import qualified GHC.Runtime.Heap.Inspect as GHCI-import qualified GHCi.Message as GHCi-import qualified GHC.Unit.Home.Graph as HUG -import Data.Maybe-import Control.Monad.Reader-import Data.IORef-+import GHC.Debugger.Breakpoint+import GHC.Debugger.Evaluation+import GHC.Debugger.Stopped import GHC.Debugger.Monad+import GHC.Debugger.Utils import GHC.Debugger.Interface.Messages  --------------------------------------------------------------------------------@@ -86,624 +50,4 @@   TerminateProcess -> liftIO $ do     -- Terminate!     exitWith ExitSuccess------------------------------------------------------------------------------------- * Breakpoints------------------------------------------------------------------------------------- | Remove all module breakpoints set on the given loaded module by path------ If the argument is @Nothing@, clear all function breakpoints instead.-clearBreakpoints :: Maybe FilePath -> Debugger ()-clearBreakpoints mfile = do-  -- It would be simpler to go to all loaded modules and disable all-  -- breakpoints for that module rather than keeping track,-  -- but much less efficient at scale.-  hsc_env <- getSession-  bids <- getActiveBreakpoints mfile-  forM_ bids $ \bid -> do-    GHC.setupBreakpoint hsc_env bid (breakpointStatusInt BreakpointDisabled)--  -- Clear out the state-  bpsRef <- asks activeBreakpoints-  liftIO $ writeIORef bpsRef emptyModuleEnv---- | Find a 'BreakpointId' and its span from a module + line + column.------ Used by 'setBreakpoints' and 'GetBreakpointsAt' requests-getBreakpointsAt :: ModSummary {-^ module -} -> Int {-^ line num -} -> Maybe Int {-^ column num -} -> Debugger (Maybe (BreakIndex, RealSrcSpan))-getBreakpointsAt modl lineNum columnNum = do-  -- TODO: Cache moduleLineMap.-  mticks <- makeModuleLineMap (ms_mod modl)-  let mbid = do-        ticks <- mticks-        case columnNum of-          Nothing -> findBreakByLine lineNum ticks-          Just col -> findBreakByCoord (lineNum, col) ticks-  return mbid---- | Set a breakpoint in this session-setBreakpoint :: Breakpoint -> BreakpointStatus -> Debugger BreakFound-setBreakpoint ModuleBreak{path, lineNum, columnNum} bp_status = do-  mmodl <- getModuleByPath path-  case mmodl of-    Left e -> do-      displayWarnings [e]-      return BreakNotFound-    Right modl -> do-      mbid <- getBreakpointsAt modl lineNum columnNum--      case mbid of-        Nothing -> return BreakNotFound-        Just (bix, span) -> do-          let bid = BreakpointId { bi_tick_mod = ms_mod modl-                                 , bi_tick_index = bix }-          changed <- registerBreakpoint bid bp_status ModuleBreakpointKind-          return $ BreakFound-            { changed = changed-            , sourceSpan = realSrcSpanToSourceSpan span-            , breakId = bid-            }-setBreakpoint FunctionBreak{function} bp_status = do-  logger <- getLogger-  resolveFunctionBreakpoint function >>= \case-    Left e -> error (showPprUnsafe e)-    Right (modl, mod_info, fun_str) -> do-      let modBreaks = GHC.modInfoModBreaks mod_info-          applyBreak (bix, span) = do-            let bid = BreakpointId { bi_tick_mod = modl-                                   , bi_tick_index = bix }-            changed <- registerBreakpoint bid bp_status FunctionBreakpointKind-            return $ BreakFound-              { changed = changed-              , sourceSpan = realSrcSpanToSourceSpan span-              , breakId = bid-              }-      case findBreakForBind fun_str modBreaks of-        []  -> do-          liftIO $ logOutput logger (text $ "No breakpoint found by name " ++ function ++ ". Ignoring...")-          return BreakNotFound-        [b] -> applyBreak b-        bs  -> do-          liftIO $ logOutput logger (text $ "Ambiguous breakpoint found by name " ++ function ++ ": " ++ show bs ++ ". Setting breakpoints in all...")-          ManyBreaksFound <$> mapM applyBreak bs-setBreakpoint exception_bp bp_status = do-  let ch_opt | BreakpointDisabled <- bp_status-             = gopt_unset-             | otherwise-             = gopt_set-      opt | OnUncaughtExceptionsBreak <- exception_bp-          = Opt_BreakOnError-          | OnExceptionsBreak <- exception_bp-          = Opt_BreakOnException-  dflags <- GHC.getInteractiveDynFlags-  let-    -- changed if option is ON and bp is OFF (breakpoint disabled), or if-    -- option is OFF and bp is ON (i.e. XOR)-    breakOn = bp_status /= BreakpointDisabled-    didChange = gopt opt dflags `xor` breakOn-  GHC.setInteractiveDynFlags $ dflags `ch_opt` opt-  return (BreakFoundNoLoc didChange)------------------------------------------------------------------------------------- * Evaluation------------------------------------------------------------------------------------- | Run a program with debugging enabled-debugExecution :: EntryPoint -> [String] {-^ Args -} -> Debugger EvalResult-debugExecution entry args = do--  -- consider always using :trace like ghci-dap to always have a stacktrace?-  -- better solution could involve profiling stack traces or from IPE info?--  (entryExp, exOpts) <- case entry of--    MainEntry nm -> do-      let prog = fromMaybe "main" nm-      wrapper <- mkEvalWrapper prog args -- bit weird that the prog name is the expression but fine-      let execWrap' fhv = GHCi.EvalApp (GHCi.EvalThis wrapper) (GHCi.EvalThis fhv)-          opts = GHC.execOptions {execWrap = execWrap'}-      return (prog, opts)--    FunctionEntry fn ->-      -- TODO: if "args" is unescaped (e.g. "some", "thing"), then "some" and-      -- "thing" will be interpreted as variables. To pass strings it needs to-      -- be "\"some\"" "\"things\"".-      return (fn ++ " " ++ unwords args, GHC.execOptions)--  GHC.execStmt entryExp exOpts >>= handleExecResult--  where-    -- It's not ideal to duplicate these two functions from ghci, but its unclear where they would better live. Perhaps next to compileParsedExprRemote? The issue is run-    mkEvalWrapper :: GhcMonad m => String -> [String] ->  m ForeignHValue-    mkEvalWrapper progname' args' =-      runInternal $ GHC.compileParsedExprRemote-      $ evalWrapper' `GHC.mkHsApp` nlHsString progname'-                     `GHC.mkHsApp` nlList (map nlHsString args')-      where-        nlHsString = nlHsLit . mkHsString-        evalWrapper' =-          GHC.nlHsVar $ RdrName.mkOrig gHC_INTERNAL_GHCI_HELPERS (mkVarOccFS (fsLit "evalWrapper"))--    -- run internal here serves to overwrite certain flags while executing the-    -- internal "evalWrapper" computation which is not relevant to the user.-    runInternal :: GhcMonad m => m a -> m a-    runInternal =-        withTempSession mkTempSession-      where-        mkTempSession = hscUpdateFlags (\dflags -> dflags-          { -- Disable dumping of any data during evaluation of GHCi's internal expressions. (#17500)-            dumpFlags = mempty-          }-              -- We depend on -fimplicit-import-qualified to compile expr-              -- with fully qualified names without imports (gHC_INTERNAL_GHCI_HELPERS above).-              `gopt_set` Opt_ImplicitImportQualified-          )----- | Resume execution of the stopped debuggee program-doContinue :: Debugger EvalResult-doContinue = do-  leaveSuspendedState-  GHC.resumeExec RunToCompletion Nothing-    >>= handleExecResult---- | Resume execution but only take a single step.-doSingleStep :: Debugger EvalResult-doSingleStep = do-  leaveSuspendedState-  GHC.resumeExec SingleStep Nothing-    >>= handleExecResult---- | Resume execution but stop at the next tick within the same function.------ To do a local step, we get the SrcSpan of the current suspension state and--- get its 'enclosingTickSpan' to use as a filter for breakpoints in the call--- to 'resumeExec'. Execution will only stop at breakpoints whose span matches--- this enclosing span.-doLocalStep :: Debugger EvalResult-doLocalStep = do-  leaveSuspendedState-  mb_span <- getCurrentBreakSpan-  case mb_span of-    Nothing -> error "not stopped at a breakpoint?!"-    Just (UnhelpfulSpan _) -> do-      liftIO $ putStrLn "Stopped at an exception. Forcing step into..."-      GHC.resumeExec SingleStep Nothing >>= handleExecResult-    Just loc -> do-      md <- fromMaybe (error "doLocalStep") <$> getCurrentBreakModule-      -- TODO: Cache moduleLineMap.-      ticks <- fromMaybe (error "doLocalStep:getTicks") <$> makeModuleLineMap md-      let current_toplevel_decl = enclosingTickSpan ticks loc-      GHC.resumeExec (LocalStep (RealSrcSpan current_toplevel_decl mempty)) Nothing >>= handleExecResult---- | Evaluate expression. Includes context of breakpoint if stopped at one (the current interactive context).-doEval :: String -> Debugger EvalResult-doEval exp = do-  excr <- (Right <$> GHC.execStmt exp GHC.execOptions) `catch` \(e::SomeException) -> pure (Left (displayException e))-  case excr of-    Left err -> pure $ EvalAbortedWith err-    Right ExecBreak{} -> continueToCompletion >>= handleExecResult-    Right r@ExecComplete{} -> handleExecResult r---- | Turn a GHC's 'ExecResult' into an 'EvalResult' response-handleExecResult :: GHC.ExecResult -> Debugger EvalResult-handleExecResult = \case-    ExecComplete {execResult} -> do-      case execResult of-        Left e -> return (EvalException (show e) "SomeException")-        Right [] -> return (EvalCompleted "" "") -- Evaluation completed without binding any result.-        Right (n:_ns) -> inspectName n >>= \case-          Just VarInfo{varValue, varType} -> return (EvalCompleted varValue varType)-          Nothing     -> liftIO $ fail "doEval failed"-    ExecBreak {breakNames = _, breakPointId = Nothing} ->-      -- Stopped at an exception-      -- TODO: force the exception to display string with Backtrace?-      return EvalStopped{breakId = Nothing}-    ExecBreak {breakNames = _, breakPointId} ->-      return EvalStopped{breakId = toBreakpointId <$> breakPointId}--{--Note [Don't crash if not stopped]-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-Requests such as `stacktrace`, `scopes`, or `variables` may end up-coming after the execution of a program has terminated. For instance,-consider this interleaving:--1. SENT Stopped event         <-- we're stopped-2. RECEIVED StackTrace req    <-- client issues after stopped event-3. RECEIVED Next req          <-- user clicks step-next-4. <program execution resumes and fails>-5. SENT Terminate event       <-- execution failed and we report it to exit cleanly-6. RECEIVED Scopes req        <-- happens as a sequence of 2 that wasn't canceled-7. <used to crash! because we're no longer at a breakpoint>--Now, we simply returned empty responses when these requests come in-while we're no longer at a breakpoint. The client will soon come to a halt-because of the termination event we sent.--}------------------------------------------------------------------------------------- * Stack trace------------------------------------------------------------------------------------- | Get the stack frames at the point we're stopped at-getStacktrace :: Debugger [StackFrame]-getStacktrace = GHC.getResumeContext >>= \case-  [] ->-    -- See Note [Don't crash if not stopped]-    return []-  r:_-    | Just ss <- srcSpanToRealSrcSpan (GHC.resumeSpan r)-    -> return-        [ StackFrame-          { name = GHC.resumeDecl r-          , sourceSpan = realSrcSpanToSourceSpan ss-          }-        ]-    | otherwise ->-        -- No resume span; which should mean we're stopped on an exception.-        -- No info for now.-        return []------------------------------------------------------------------------------------- * Scopes------------------------------------------------------------------------------------- | Get the stack frames at the point we're stopped at-getScopes :: Debugger [ScopeInfo]-getScopes = GHC.getCurrentBreakSpan >>= \case-  Nothing ->-    -- See Note [Don't crash if not stopped]-    return []-  Just span-    | Just rss <- srcSpanToRealSrcSpan span-    , let sourceSpan = realSrcSpanToSourceSpan rss-    -> do-      -- It is /very important/ to report a number of variables (numVars) for-      -- larger scopes. If we just say "Nothing", then all variables of all-      -- scopes will be fetched at every stopped event.-      curr_modl <- expectJust <$> getCurrentBreakModule-      hsc_env <- getSession-      in_mod <- getTopEnv curr_modl-      imported <- getTopImported curr_modl-      return-        [ ScopeInfo { kind = LocalVariablesScope-                    , expensive = False-                    , numVars = Nothing-                    , sourceSpan-                    }-        , ScopeInfo { kind = ModuleVariablesScope-                    , expensive = True-                    , numVars = Just (sizeUFM in_mod)-                    , sourceSpan-                    }-        , ScopeInfo { kind = GlobalVariablesScope-                    , expensive = True-#if MIN_VERSION_ghc(9,13,20250417)-                    , numVars = Just (sizeOccEnv imported)-#else-                    , numVars = Nothing-#endif-                    , sourceSpan-                    }-        ]-    | otherwise ->-      -- No resume span; which should mean we're stopped on an exception-      -- TODO: Use exception context to create source span, or at least-      -- return the source span null to have Scopes at least.-      return []------------------------------------------------------------------------------------- * Variables------------------------------------------------------------------------------------ Note [Variables Requests]--- ~~~~~~~~~~~~~~~~~~~~~~~~~--- We can receive a Variables request for three different reasons------ 1. To get the variables in a certain scope--- 2. To inspect the value of a lazy variable--- 3. To expand the structure of a variable------ The replies are, respectively:------ (VARR)--- (a) All the variables in the request scope--- (b) ONLY the variable requested--- (c) The fields of the variable requested but NOT the original variable---- | Get variables using a variable/variables reference------ If the Variable Request ends up being case (VARR)(b), then we signal the--- request forced the variable and return @Left varInfo@. Otherwise, @Right vis@.------ See Note [Variables Requests]-getVariables :: VariableReference -> Debugger (Either VarInfo [VarInfo])-getVariables vk = do-  hsc_env <- getSession-  GHC.getResumeContext >>= \case-    [] ->-      -- See Note [Don't crash if not stopped]-      return (Right [])-    r:_ -> case vk of--      -- Only `seq` the variable when inspecting a specific one (`SpecificVariable`)-      -- (VARR)(b,c)-      SpecificVariable i -> do-        lookupVarByReference i >>= \case-          Nothing -> error "lookupVarByReference failed"-          Just (n, term) -> do-            let ty = GHCI.termType term-            term' <- if isBoringTy ty-                        then deepseqTerm term -- deepseq boring types like String, because it is more helpful to print them whole than their structure.-                        else seqTerm term-            -- insertVarReference i n term' -- update with evaluated term?-            vi <- termToVarInfo n term'-            case term {- original term -} of--              -- (VARR)(b)-              Suspension{} -> do-                -- Original Term was a suspension:-                -- It is a "lazy" DAP variable, so our reply can ONLY include-                -- this single variable. So we erase the @varFields@ after the fact.-                return (Left vi{varFields = NoFields})--              -- (VARR)(c)-              _ -> Right <$> do-                -- Original Term was already something other than a Suspension;-                -- Meaning the @SpecificVariable@ request means to inspect the structure.-                -- Return ONLY the fields-                case varFields vi of-                  NoFields -> return []-                  LabeledFields xs -> return xs-                  IndexedFields xs -> return xs--      -- (VARR)(a) from here onwards--      LocalVariables -> fmap Right $-        -- bindLocalsAtBreakpoint hsc_env (GHC.resumeApStack r) (GHC.resumeSpan r) (GHC.resumeBreakpointId r)-        mapM (tyThingToVarInfo defaultDepth) =<< GHC.getBindings--      ModuleVariables -> Right <$> do-        case ibi_tick_mod <$> GHC.resumeBreakpointId r of-          Nothing -> return []-          Just curr_modl -> do-            things <- typeEnvElts <$> getTopEnv curr_modl-            mapM (\tt -> do-              nameStr <- display (getName tt)-              vi <- tyThingToVarInfo 1 tt-              return vi{varName = nameStr}) things--      GlobalVariables -> Right <$> do-        case ibi_tick_mod <$> GHC.resumeBreakpointId r of-          Nothing -> return []-          Just curr_modl -> do-            hsc_env <- getSession-            names <- map greName . globalRdrEnvElts <$> getTopImported curr_modl-            mapM (\n-> do-              nameStr <- display n-              liftIO (GHC.lookupType hsc_env n) >>= \case-                Nothing ->-                  return VarInfo-                    { varName = nameStr-                    , varType = ""-                    , varValue = ""-                    , isThunk = False-                    , varRef = NoVariables-                    , varFields = NoFields-                    }-                Just tt -> do-                  vi <- tyThingToVarInfo 1 tt {- don't look deep for global and mod vars -}-                  return vi{varName = nameStr}-              ) names--      NoVariables -> Right <$> do-        return []--defaultDepth =  5 -- the depth determines how much of the structure is traversed.-                  -- using a small value like 5 here is what causes the-                  -- structure to be improperly rendered inline with many underscores.-                  -- Note: GHCi uses depth=100-                  -- TODO: Investigate why this isn't fast enough to use 100.-                  -- TODO: We need a new metric to determine how much we force.-                  -- Depth is not good enough because e.g for a very broad-                  -- recursive type it will be exponentially many nodes to-                  -- visit-                  -- For now, try depth=5------------------------------------------------------------------------------------- * GHC Utilities------------------------------------------------------------------------------------- | All top-level things from a module, including unexported ones.-getTopEnv :: Module -> Debugger TypeEnv-getTopEnv modl = do-  hsc_env <- getSession-  liftIO $ HUG.lookupHugByModule modl (hsc_HUG hsc_env) >>= \case-    Nothing -> return emptyTypeEnv-    Just HomeModInfo-      { hm_details = ModDetails-        { md_types = things-        }-      } -> return things---- | All bindings imported at a given module-getTopImported :: Module -> Debugger GlobalRdrEnv-getTopImported modl = do-  hsc_env <- getSession-  liftIO $ HUG.lookupHugByModule modl (hsc_HUG hsc_env) >>= \case-    Nothing -> return emptyGlobalRdrEnv-#if MIN_VERSION_ghc(9,13,20250417)-    Just hmi -> mkTopLevImportedEnv hsc_env hmi-#else-    Just hmi -> return emptyGlobalRdrEnv-#endif---- | Get the value and type of a given 'Name' as rendered strings in 'VarInfo'.-inspectName :: Name -> Debugger (Maybe VarInfo)-inspectName n = do-  GHC.lookupName n >>= \case-    Nothing -> do-      liftIO . putStrLn =<< display (text "Failed to lookup name: " <+> ppr n)-      pure Nothing-    Just tt -> Just <$> tyThingToVarInfo defaultDepth tt---- | 'TyThing' to 'VarInfo'. The 'Bool' argument indicates whether to force the--- value of the thing (as in @True = :force@, @False = :print@)-tyThingToVarInfo :: Int {-^ Depth -} -> TyThing -> Debugger VarInfo-tyThingToVarInfo depth0 = \case-  t@(AConLike c) -> VarInfo <$> display c <*> display t <*> display t <*> pure False <*> pure NoVariables <*> pure NoFields-  t@(ATyCon c)   -> VarInfo <$> display c <*> display t <*> display t <*> pure False <*> pure NoVariables <*> pure NoFields-  t@(ACoAxiom c) -> VarInfo <$> display c <*> display t <*> display t <*> pure False <*> pure NoVariables <*> pure NoFields-  AnId i -> do-    -- For boring types we want to get the value as it is (by traversing it to-    -- the end), rather than stopping short and returning a suspension (e.g.-    -- for the string tail), because boring types are printed whole rather than-    -- being represented by an expandable structure.-    let depth1 = if isBoringTy (GHC.idType i) then maxBound else depth0-    term <- GHC.obtainTermFromId depth1 False{-don't force-} i-    termToVarInfo (GHC.idName i) term---- | Construct a 'VarInfo' from the given 'Name' of the variable and the 'Term' it binds-termToVarInfo :: Name -> Term -> Debugger VarInfo-termToVarInfo top_name top_term = do--  -- Make a VarInfo for the top term.-  top_vi <- go top_name top_term--  sub_vis <- case top_term of-      -- Boring types don't get subfields-      _ | isBoringTy (GHCI.termType top_term) ->-        return NoFields--      -- Make 'VarInfo's for the first layer of subTerms only.-      Term{dc=Right dc, subTerms} -> do-        case dataConFieldLabels dc of-          -- Not a record type,-          -- Use indexed fields-          [] -> do-            let names = zipWith (\ix _ -> mkIndexVar ix) [1..] (dataConOrigArgTys dc)-            IndexedFields <$> mapM (uncurry go) (zipEqual names subTerms)-          -- Is a record type,-          -- Use field labels-          dataConFields -> do-            let names = map flSelector dataConFields-            LabeledFields <$> mapM (uncurry go) (zipEqual names subTerms)-      NewtypeWrap{dc=Right dc, wrapped_term} -> do-        case dataConFieldLabels dc of-          [] -> do-            let name = mkIndexVar 1-            wvi <- go name wrapped_term-            return (IndexedFields [wvi])-          [fld] -> do-            let name = flSelector fld-            wvi <- go name wrapped_term-            return (LabeledFields [wvi])-          _ -> error "unexpected number of Newtype fields: larger than 1"-      _ -> return NoFields--  return top_vi{varFields = sub_vis}--  where-    -- Make a VarInfo for a term, but don't recurse into the fields and return-    -- @NoFields@ for 'varFields'.-    ---    -- We do this because we don't want to recursively return all sub-fields ---    -- only the first layer of fields for the top term.-    go n term = do-      let-        varFields = NoFields-        isThunk-          -- to have more information we could match further on @Heap.ClosureType@-         | Suspension{} <- term = True-         | otherwise = False-        ty = GHCI.termType term--        -- We scrape the subterms to display as the var's value. The structure is-        -- displayed in the editor itself by expanding the variable sub-fields-        -- (`varFields`). -        termHead t-          -- But show strings and lits in full-          | isBoringTy ty = t-          | otherwise     = case t of-             Term{}                    -> t{subTerms = []}-             NewtypeWrap{wrapped_term} -> t{wrapped_term = termHead wrapped_term}-             _                         -> t-      varName <- display n-      varType <- display ty-      varValue <- display =<< GHCD.showTerm (termHead term)-      -- liftIO $ print (varName, varType, varValue, GHCI.isFullyEvaluatedTerm term)--      -- The VarReference allows user to expand variable structure and inspect its value.-      -- Here, we do not want to allow expanding a term that is fully evaluated.-      -- We only want to return @SpecificVariable@ (which allows expansion) for-      -- values with sub-fields or thunks.-      varRef <- do-        if GHCI.isFullyEvaluatedTerm term-           -- Even if it is already evaluated, we do want to display a-           -- structure as long if it is not a "boring type" (one that does not-           -- provide useful information from being expanded)-           -- (e.g. consider how awkward it is to expand Char# 10 and I# 20)-           && (isBoringTy ty || not (hasDirectSubTerms term))-         then-            return NoVariables-         else do-            ir <- freshInt-            insertVarReference ir n term-            return (SpecificVariable ir)--      return VarInfo{..}--    hasDirectSubTerms = \case-      Suspension{}   -> False-      Prim{}         -> False-      NewtypeWrap{}  -> True-      RefWrap{}      -> True-      Term{subTerms} -> not $ null subTerms--    mkIndexVar ix = mkUnboundName (mkVarOcc ("_" ++ show @Int ix))---- | A boring type is one for which we don't care about the structure and would--- rather see "whole" when being inspected. Strings and literals are a good--- example, because it's more useful to see the string value than it is to see--- a linked list of characters where each has to be forced individually.-isBoringTy :: Type -> Bool-isBoringTy t = isDoubleTy t || isFloatTy t || isIntTy t || isWordTy t || isStringTy t-                || isIntegerTy t || isNaturalTy t || isCharTy t---- | Whenever we run a request that continues execution from the current--- suspended state, such as Next,Step,Continue, this function should be called--- to delete the variable references that become invalid as we leave the--- suspended state.------ In particular, @'varReferences'@ is reset.------ See also section "Lifetime of Objects References" in the DAP specification.-leaveSuspendedState :: Debugger ()-leaveSuspendedState = do-  -- TODO:-  --  [ ] Preserve bindings introduced by evaluate requests-  ioref <- asks varReferences-  liftIO $ writeIORef ioref mempty---- | Convert a GHC's src span into an interface one-realSrcSpanToSourceSpan :: RealSrcSpan -> SourceSpan-realSrcSpanToSourceSpan ss = SourceSpan-  { file = unpackFS $ srcSpanFile ss-  , startLine = srcSpanStartLine ss-  , startCol = srcSpanStartCol ss-  , endLine = srcSpanEndLine ss-  , endCol = srcSpanEndCol ss-  }------------------------------------------------------------------------------------- * General utilities------------------------------------------------------------------------------------- | Display an Outputable value as a String-display :: Outputable a => a -> Debugger String-display x = do-  dflags <- getDynFlags-  return $ showSDoc dflags (ppr x)-{-# INLINE display #-} 
+ ghc-debugger/GHC/Debugger/Breakpoint.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,+   DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,+   TypeApplications, ScopedTypeVariables, BangPatterns #-}+module GHC.Debugger.Breakpoint where++import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Reader+import Data.IORef+import Data.Bits (xor)++import GHC+#if MIN_VERSION_ghc(9,13,20250417)+import GHC.Types.Name.Occurrence (sizeOccEnv)+#endif+import GHC.Utils.Error (logOutput)+import GHC.Driver.DynFlags as GHC+import GHC.Driver.Ppr as GHC+import GHC.Runtime.Debugger.Breakpoints as GHC+import GHC.Unit.Module.Env as GHC+import GHC.Utils.Outputable as GHC++import GHC.Debugger.Monad+import GHC.Debugger.Utils+import GHC.Debugger.Interface.Messages++--------------------------------------------------------------------------------+-- * Breakpoints+--------------------------------------------------------------------------------++-- | Remove all module breakpoints set on the given loaded module by path+--+-- If the argument is @Nothing@, clear all function breakpoints instead.+clearBreakpoints :: Maybe FilePath -> Debugger ()+clearBreakpoints mfile = do+  -- It would be simpler to go to all loaded modules and disable all+  -- breakpoints for that module rather than keeping track,+  -- but much less efficient at scale.+  hsc_env <- getSession+  bids <- getActiveBreakpoints mfile+  forM_ bids $ \bid -> do+    GHC.setupBreakpoint hsc_env bid (breakpointStatusInt BreakpointDisabled)++  -- Clear out the state+  bpsRef <- asks activeBreakpoints+  liftIO $ writeIORef bpsRef emptyModuleEnv++-- | Find a 'BreakpointId' and its span from a module + line + column.+--+-- Used by 'setBreakpoints' and 'GetBreakpointsAt' requests+getBreakpointsAt :: ModSummary {-^ module -} -> Int {-^ line num -} -> Maybe Int {-^ column num -} -> Debugger (Maybe (BreakIndex, RealSrcSpan))+getBreakpointsAt modl lineNum columnNum = do+  -- TODO: Cache moduleLineMap.+  mticks <- makeModuleLineMap (ms_mod modl)+  let mbid = do+        ticks <- mticks+        case columnNum of+          Nothing -> findBreakByLine lineNum ticks+          Just col -> findBreakByCoord (lineNum, col) ticks+  return mbid++-- | Set a breakpoint in this session+setBreakpoint :: Breakpoint -> BreakpointStatus -> Debugger BreakFound+setBreakpoint ModuleBreak{path, lineNum, columnNum} bp_status = do+  mmodl <- getModuleByPath path+  case mmodl of+    Left e -> do+      displayWarnings [e]+      return BreakNotFound+    Right modl -> do+      mbid <- getBreakpointsAt modl lineNum columnNum++      case mbid of+        Nothing -> return BreakNotFound+        Just (bix, spn) -> do+          let bid = BreakpointId { bi_tick_mod = ms_mod modl+                                 , bi_tick_index = bix }+          changed <- registerBreakpoint bid bp_status ModuleBreakpointKind+          return $ BreakFound+            { changed = changed+            , sourceSpan = realSrcSpanToSourceSpan spn+            , breakId = bid+            }+setBreakpoint FunctionBreak{function} bp_status = do+  logger <- getLogger+  resolveFunctionBreakpoint function >>= \case+    Left e -> error (showPprUnsafe e)+    Right (modl, mod_info, fun_str) -> do+      let modBreaks = GHC.modInfoModBreaks mod_info+          applyBreak (bix, spn) = do+            let bid = BreakpointId { bi_tick_mod = modl+                                   , bi_tick_index = bix }+            changed <- registerBreakpoint bid bp_status FunctionBreakpointKind+            return $ BreakFound+              { changed = changed+              , sourceSpan = realSrcSpanToSourceSpan spn+              , breakId = bid+              }+      case findBreakForBind fun_str modBreaks of+        []  -> do+          liftIO $ logOutput logger (text $ "No breakpoint found by name " ++ function ++ ". Ignoring...")+          return BreakNotFound+        [b] -> applyBreak b+        bs  -> do+          liftIO $ logOutput logger (text $ "Ambiguous breakpoint found by name " ++ function ++ ": " ++ show bs ++ ". Setting breakpoints in all...")+          ManyBreaksFound <$> mapM applyBreak bs+setBreakpoint exception_bp bp_status = do+  let ch_opt | BreakpointDisabled <- bp_status+             = gopt_unset+             | otherwise+             = gopt_set+      opt | OnUncaughtExceptionsBreak <- exception_bp+          = Opt_BreakOnError+          | OnExceptionsBreak <- exception_bp+          = Opt_BreakOnException+  dflags <- GHC.getInteractiveDynFlags+  let+    -- changed if option is ON and bp is OFF (breakpoint disabled), or if+    -- option is OFF and bp is ON (i.e. XOR)+    breakOn = bp_status /= BreakpointDisabled+    didChange = gopt opt dflags `xor` breakOn+  GHC.setInteractiveDynFlags $ dflags `ch_opt` opt+  return (BreakFoundNoLoc didChange)
+ ghc-debugger/GHC/Debugger/Evaluation.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,+   DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,+   TypeApplications, ScopedTypeVariables, BangPatterns #-}+module GHC.Debugger.Evaluation where++import Control.Monad.IO.Class+import Control.Monad.Catch+import Data.Maybe++import GHC+#if MIN_VERSION_ghc(9,13,20250417)+import GHC.Types.Name.Occurrence (sizeOccEnv)+#endif+import GHC.Builtin.Names (gHC_INTERNAL_GHCI_HELPERS)+import GHC.Data.FastString+import GHC.Driver.DynFlags as GHC+import GHC.Driver.Env as GHC+import GHC.Driver.Monad+import GHC.Runtime.Debugger.Breakpoints as GHC+import GHC.Types.Breakpoint+import GHC.Types.Name.Occurrence (mkVarOccFS)+import GHC.Types.Name.Reader as RdrName (mkOrig)+import GHC.Utils.Outputable as GHC+import qualified GHCi.Message as GHCi++import GHC.Debugger.Stopped.Variables+import GHC.Debugger.Monad+import GHC.Debugger.Utils+import GHC.Debugger.Interface.Messages++--------------------------------------------------------------------------------+-- * Evaluation+--------------------------------------------------------------------------------++-- | Run a program with debugging enabled+debugExecution :: EntryPoint -> [String] {-^ Args -} -> Debugger EvalResult+debugExecution entry args = do++  -- consider always using :trace like ghci-dap to always have a stacktrace?+  -- better solution could involve profiling stack traces or from IPE info?++  (entryExp, exOpts) <- case entry of++    MainEntry nm -> do+      let prog = fromMaybe "main" nm+      wrapper <- mkEvalWrapper prog args -- bit weird that the prog name is the expression but fine+      let execWrap' fhv = GHCi.EvalApp (GHCi.EvalThis wrapper) (GHCi.EvalThis fhv)+          opts = GHC.execOptions {execWrap = execWrap'}+      return (prog, opts)++    FunctionEntry fn ->+      -- TODO: if "args" is unescaped (e.g. "some", "thing"), then "some" and+      -- "thing" will be interpreted as variables. To pass strings it needs to+      -- be "\"some\"" "\"things\"".+      return (fn ++ " " ++ unwords args, GHC.execOptions)++  GHC.execStmt entryExp exOpts >>= handleExecResult++  where+    -- It's not ideal to duplicate these two functions from ghci, but its unclear where they would better live. Perhaps next to compileParsedExprRemote? The issue is run+    mkEvalWrapper :: GhcMonad m => String -> [String] ->  m ForeignHValue+    mkEvalWrapper progname' args' =+      runInternal $ GHC.compileParsedExprRemote+      $ evalWrapper' `GHC.mkHsApp` nlHsString progname'+                     `GHC.mkHsApp` nlList (map nlHsString args')+      where+        nlHsString = nlHsLit . mkHsString+        evalWrapper' =+          GHC.nlHsVar $ RdrName.mkOrig gHC_INTERNAL_GHCI_HELPERS (mkVarOccFS (fsLit "evalWrapper"))++    -- run internal here serves to overwrite certain flags while executing the+    -- internal "evalWrapper" computation which is not relevant to the user.+    runInternal :: GhcMonad m => m a -> m a+    runInternal =+        withTempSession mkTempSession+      where+        mkTempSession = hscUpdateFlags (\dflags -> dflags+          { -- Disable dumping of any data during evaluation of GHCi's internal expressions. (#17500)+            dumpFlags = mempty+          }+              -- We depend on -fimplicit-import-qualified to compile expr+              -- with fully qualified names without imports (gHC_INTERNAL_GHCI_HELPERS above).+              `gopt_set` Opt_ImplicitImportQualified+          )+++-- | Resume execution of the stopped debuggee program+doContinue :: Debugger EvalResult+doContinue = do+  leaveSuspendedState+  GHC.resumeExec RunToCompletion Nothing+    >>= handleExecResult++-- | Resume execution but only take a single step.+doSingleStep :: Debugger EvalResult+doSingleStep = do+  leaveSuspendedState+  GHC.resumeExec SingleStep Nothing+    >>= handleExecResult++-- | Resume execution but stop at the next tick within the same function.+--+-- To do a local step, we get the SrcSpan of the current suspension state and+-- get its 'enclosingTickSpan' to use as a filter for breakpoints in the call+-- to 'resumeExec'. Execution will only stop at breakpoints whose span matches+-- this enclosing span.+doLocalStep :: Debugger EvalResult+doLocalStep = do+  leaveSuspendedState+  mb_span <- getCurrentBreakSpan+  case mb_span of+    Nothing -> error "not stopped at a breakpoint?!"+    Just (UnhelpfulSpan _) -> do+      liftIO $ putStrLn "Stopped at an exception. Forcing step into..."+      GHC.resumeExec SingleStep Nothing >>= handleExecResult+    Just loc -> do+      md <- fromMaybe (error "doLocalStep") <$> getCurrentBreakModule+      -- TODO: Cache moduleLineMap.+      ticks <- fromMaybe (error "doLocalStep:getTicks") <$> makeModuleLineMap md+      let current_toplevel_decl = enclosingTickSpan ticks loc+      GHC.resumeExec (LocalStep (RealSrcSpan current_toplevel_decl mempty)) Nothing >>= handleExecResult++-- | Evaluate expression. Includes context of breakpoint if stopped at one (the current interactive context).+doEval :: String -> Debugger EvalResult+doEval expr = do+  excr <- (Right <$> GHC.execStmt expr GHC.execOptions) `catch` \(e::SomeException) -> pure (Left (displayException e))+  case excr of+    Left err -> pure $ EvalAbortedWith err+    Right ExecBreak{} -> continueToCompletion >>= handleExecResult+    Right r@ExecComplete{} -> handleExecResult r++-- | Turn a GHC's 'ExecResult' into an 'EvalResult' response+handleExecResult :: GHC.ExecResult -> Debugger EvalResult+handleExecResult = \case+    ExecComplete {execResult} -> do+      case execResult of+        Left e -> return (EvalException (show e) "SomeException")+        Right [] -> return (EvalCompleted "" "") -- Evaluation completed without binding any result.+        Right (n:_ns) -> inspectName n >>= \case+          Just VarInfo{varValue, varType} -> return (EvalCompleted varValue varType)+          Nothing     -> liftIO $ fail "doEval failed"+    ExecBreak {breakNames = _, breakPointId = Nothing} ->+      -- Stopped at an exception+      -- TODO: force the exception to display string with Backtrace?+      return EvalStopped{breakId = Nothing}+    ExecBreak {breakNames = _, breakPointId} ->+      return EvalStopped{breakId = toBreakpointId <$> breakPointId}++-- | Get the value and type of a given 'Name' as rendered strings in 'VarInfo'.+inspectName :: Name -> Debugger (Maybe VarInfo)+inspectName n = do+  GHC.lookupName n >>= \case+    Nothing -> do+      liftIO . putStrLn =<< display (text "Failed to lookup name: " <+> ppr n)+      pure Nothing+    Just tt -> Just <$> tyThingToVarInfo tt+
ghc-debugger/GHC/Debugger/Interface/Messages.hs view
@@ -110,9 +110,6 @@       , varRef   :: VariableReference       -- ^ A reference back to this variable -      , varFields :: VarFields-      -- ^ A 'VarInfo' for each field. These may be named (@Left@) or indexed fields (@Right@).-       -- TODO:       --  memory reference using ghc-debug.       }
ghc-debugger/GHC/Debugger/Monad.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE BangPatterns, CPP, GeneralizedNewtypeDeriving, NamedFieldPuns, TupleSections, LambdaCase, OverloadedRecordDot #-}+{-# LANGUAGE BangPatterns, CPP, GeneralizedNewtypeDeriving, NamedFieldPuns, TupleSections, LambdaCase, OverloadedRecordDot, TypeApplications #-} module GHC.Debugger.Monad where  import Prelude hiding (mod)@@ -13,6 +13,8 @@ import Control.Monad.Catch  import GHC+import GHC.Types.Name (mkDerivedInternalName)+import GHC.Types.Name.Occurrence (mkVarOcc) import qualified GHCi.BreakArray as BA import GHC.Driver.DynFlags as GHC import GHC.Driver.Phases as GHC@@ -28,6 +30,7 @@ import GHC.Runtime.Interpreter as GHCi import GHC.Runtime.Heap.Inspect import GHC.Unit.Module.Env as GHC+import GHC.Types.Name.Env import GHC.Driver.Env  import Data.IORef@@ -39,6 +42,8 @@ import Control.Monad.Reader  import GHC.Debugger.Interface.Messages+import GHC.Debugger.Runtime.Term.Key+import GHC.Debugger.Runtime.Term.Cache import System.Posix.Signals  -- | A debugger action.@@ -54,13 +59,21 @@       { activeBreakpoints :: IORef (ModuleEnv (IM.IntMap (BreakpointStatus, BreakpointKind)))         -- ^ Maps a 'BreakpointId' in Trie representation to the         -- 'BreakpointStatus' it was activated with.-      , varReferences     :: IORef (IM.IntMap (Name, Term))++      , varReferences     :: IORef (IM.IntMap TermKey, TermKeyMap Int)       -- ^ When we're stopped at a breakpoint, this maps variable reference to       -- Terms to allow further inspection and forcing by reference.       --       -- This map is only valid while stopped in this context. After stepping       -- or resuming evaluation in any available way, this map becomes invalid       -- and should therefore be cleaned.+      --+      -- The TermKeyMap map is a reverse lookup map to find which references+      -- already exist for given names++      , termCache         :: IORef TermCache+      -- ^ TermCache+       , genUniq           :: IORef Int       -- ^ Generates unique ints       }@@ -299,26 +312,51 @@ --------------------------------------------------------------------------------  -- | Find a variable's associated Term and Name by reference ('Int')-lookupVarByReference :: Int -> Debugger (Maybe (Name, Term))+lookupVarByReference :: Int -> Debugger (Maybe TermKey) lookupVarByReference i = do   ioref <- asks varReferences-  rm <- readIORef ioref & liftIO+  (rm, _) <- readIORef ioref & liftIO   return $ IM.lookup i rm --- | Inserts a mapping from the given variable reference to the variable's--- associated Term and the Name it is bound to for display-insertVarReference :: Int -> Name -> Term -> Debugger ()-insertVarReference i name term = do+-- | Finds or creates an integer var reference for the given 'TermKey'.+-- TODO: Arguably, this mapping should be part of the debug-adapter, and+-- ghc-debugger should deal in 'TermKey' terms only.+getVarReference :: TermKey -> Debugger Int+getVarReference key = do+  ioref     <- asks varReferences+  (rm, tkm) <- readIORef ioref & liftIO+  (i, tkm') <- case lookupTermKeyMap key tkm of+    Nothing -> do+      new_i <- freshInt+      return (new_i, insertTermKeyMap key new_i tkm)+    Just existing_i ->+      return (existing_i, tkm)+  let rm' = IM.insert i key rm+  writeIORef ioref (rm', tkm') & liftIO+  return i++-- | Whenever we run a request that continues execution from the current+-- suspended state, such as Next,Step,Continue, this function should be called+-- to delete the variable references that become invalid as we leave the+-- suspended state.+--+-- In particular, @'varReferences'@ is reset.+--+-- See also section "Lifetime of Objects References" in the DAP specification.+leaveSuspendedState :: Debugger ()+leaveSuspendedState = do   ioref <- asks varReferences-  rm <- readIORef ioref & liftIO-  let-    rm' = IM.insert i (name, term) rm-  writeIORef ioref rm' & liftIO+  liftIO $ writeIORef ioref mempty  -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- +defaultDepth :: Int+defaultDepth =  2 -- the depth determines how much of the runtime structure is traversed.+                  -- @obtainTerm@ and friends handle fetching arbitrarily nested data structures+                  -- so we only depth enough to get to the next level of subterms.+ -- | Evaluate a suspended Term to WHNF. -- -- Used in @'getVariables'@ to reply to a variable introspection request.@@ -334,9 +372,11 @@       () <- fromEvalResult r       let         forceThunks = False {- whether to force the thunk subterms -}-        forceDepth  = 5+        forceDepth  = defaultDepth       cvObtainTerm hsc_env forceDepth forceThunks ty val-    NewtypeWrap{wrapped_term} -> seqTerm wrapped_term+    NewtypeWrap{wrapped_term} -> do+      wrapped_term' <- seqTerm wrapped_term+      return term{wrapped_term=wrapped_term'}     _ -> return term  -- | Evaluate a Term to NF@@ -381,7 +421,8 @@ -- | Initialize a 'DebuggerState' initialDebuggerState :: GHC.Ghc DebuggerState initialDebuggerState = DebuggerState <$> liftIO (newIORef emptyModuleEnv)-                                     <*> liftIO (newIORef IM.empty)+                                     <*> liftIO (newIORef mempty)+                                     <*> liftIO (newIORef mempty)                                      <*> liftIO (newIORef 0)  -- | Lift a 'Ghc' action into a 'Debugger' one.
+ ghc-debugger/GHC/Debugger/Runtime.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE GADTs, LambdaCase, NamedFieldPuns #-}+module GHC.Debugger.Runtime where++import Data.IORef+import Control.Monad.Reader+import Control.Monad.IO.Class+import qualified Data.List as L++import GHC+import GHC.Types.Id+import GHC.Types.FieldLabel+import GHC.Tc.Utils.TcType+import GHC.Runtime.Eval+import GHC.Types.Unique.Supply (uniqFromTag)+import GHC.Types.Name.Env++import GHC.Debugger.Runtime.Term.Key+import GHC.Debugger.Runtime.Term.Cache+import GHC.Debugger.Monad++-- | Obtain the runtime 'Term' from a 'TermKey'.+--+-- The 'TermKey' will be looked up in the 'TermCache' to avoid recomputing the+-- 'Term' if possible. On a cache miss the Term will be reconstructed from+-- scratch and stored in the cache.+obtainTerm :: TermKey -> Debugger Term+obtainTerm key = do+  tc_ref <- asks termCache+  tc     <- liftIO $ readIORef tc_ref+  case lookupTermCache key tc of+    -- cache miss: reconstruct, then store.+    Nothing ->+      let+        -- For boring types we want to get the value as it is (by traversing it to+        -- the end), rather than stopping short and returning a suspension (e.g.+        -- for the string tail), because boring types are printed whole rather than+        -- being represented by an expandable structure.+        depth i = if isBoringTy (GHC.idType i) then maxBound else defaultDepth++        -- Recursively get terms until we hit the desired key.+        getTerm = \case+          FromId i -> GHC.obtainTermFromId (depth i) False{-don't force-} i+          FromPath k pf -> do+            term <- getTerm k >>= \case+              -- When the key points to a Suspension, the real thing should+              -- already be forced. It's just that the shallow depth meant we+              -- returned a Suspension nonetheless while recursing in `getTerm`.+              t@Suspension{} -> do+                t' <- seqTerm t+                -- update term cache with intermediate values?+                -- insertTermCache k t'+                return t'+              t -> return t+            return $ case term of+              Term{dc=Right dc, subTerms} -> case pf of+                PositionalIndex ix -> subTerms !! (ix-1)+                LabeledField fl    ->+                  case L.findIndex (== fl) (map flSelector $ dataConFieldLabels dc) of+                    Just ix -> subTerms !! (ix-1)+                    Nothing -> error "Couldn't find labeled field in dataConFieldLabels"+              NewtypeWrap{wrapped_term} ->+                wrapped_term -- regardless of PathFragment+              _ -> error "Unexpected term for the given TermKey"+       in do+        term <- getTerm key+        liftIO $ writeIORef tc_ref (insertTermCache key term tc)+        return term++    -- cache hit+    Just hit -> return hit++-- | A boring type is one for which we don't care about the structure and would+-- rather see "whole" when being inspected. Strings and literals are a good+-- example, because it's more useful to see the string value than it is to see+-- a linked list of characters where each has to be forced individually.+isBoringTy :: Type -> Bool+isBoringTy t = isDoubleTy t || isFloatTy t || isIntTy t || isWordTy t || isStringTy t+                || isIntegerTy t || isNaturalTy t || isCharTy t++
+ ghc-debugger/GHC/Debugger/Runtime/Term/Cache.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE GADTs #-}+module GHC.Debugger.Runtime.Term.Cache where++import GHC+import GHC.Types.Id+import GHC.Tc.Utils.TcType+import GHC.Runtime.Eval+import GHC.Types.Unique.Supply (uniqFromTag)+import GHC.Types.Var.Env++import GHC.Debugger.Runtime.Term.Key++import Data.Map (Map)+import qualified Data.Map as M++--------------------------------------------------------------------------------+-- * Term Cache+--------------------------------------------------------------------------------++-- | A term cache maps Names to Terms.+--+-- We use the term cache to avoid redundant computation forcing (unique) names+-- we've already forced before.+--+-- A kind of trie map from 'TermKey's. The Map entry for no-path-fragments is+-- the 'Term' of the original 'Id'.+type TermCache = TermKeyMap Term++-- | Lookup a 'TermKey' in a 'TermCache'.+-- Returns @Nothing@ for a cache miss and @Just@ otherwise.+lookupTermCache :: TermKey -> TermCache -> Maybe Term+lookupTermCache = lookupTermKeyMap++-- | Inserts a 'Term' for the given 'TermKey' in the 'TermCache'.+--+-- Overwrites existing values.+insertTermCache :: TermKey -> Term -> TermCache -> TermCache+insertTermCache = insertTermKeyMap++--------------------------------------------------------------------------------+-- * TermKeyMap+--------------------------------------------------------------------------------++-- | Mapping from 'TermKey' to @a@. Backs 'TermCache', but is more general.+type TermKeyMap a = IdEnv (Map [PathFragment] a)++-- | Lookup a 'TermKey' in a 'TermKeyMap'.+lookupTermKeyMap :: TermKey -> TermKeyMap a -> Maybe a+lookupTermKeyMap key tc = do+  let (i, path) = unconsTermKey key+  path_map <- lookupVarEnv tc i+  M.lookup path path_map++-- | Inserts a 'Term' for the given 'TermKey' in the 'TermKeyMap'.+--+-- Overwrites existing values.+insertTermKeyMap :: TermKey -> a -> TermKeyMap a -> TermKeyMap a+insertTermKeyMap key term tc =+  let+    (i, path) = unconsTermKey key+    new_map = case lookupVarEnv tc i of+      Nothing           -> M.singleton path term+      Just existing_map -> M.insert path term existing_map+  in extendVarEnv tc i new_map
+ ghc-debugger/GHC/Debugger/Runtime/Term/Key.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE GADTs, ViewPatterns #-}+module GHC.Debugger.Runtime.Term.Key where++import Prelude hiding ((<>))++import GHC+import GHC.Utils.Outputable+import GHC.Types.Id+import GHC.Tc.Utils.TcType+import GHC.Runtime.Eval+import GHC.Types.Unique.Supply (uniqFromTag)+import GHC.Types.Name.Env++-- | A 'TermKey' serves to fetch a Term in a Debugger session.+-- Note: A 'TermKey' is only valid in the stopped context it was created in.+data TermKey where+  -- | Obtain a term from an Id.+  FromId :: Id -> TermKey++  -- | Append a PathFragment to the current Term Key. Used to construct keys+  -- for indexed and labeled fields.+  FromPath :: TermKey -> PathFragment -> TermKey++-- | A term may be identified by an 'Id' (such as a local variable) plus a list+-- of 'PathFragment's to an arbitrarily nested field.+data PathFragment+  -- | A positional index is an index from 1 to inf+  = PositionalIndex Int+  -- | A labeled field indexes a datacon fields by name+  | LabeledField Name+  deriving (Eq, Ord)++instance Outputable TermKey where+  ppr (FromId i)          = ppr i+  ppr (FromPath _ last_p) = ppr last_p++instance Outputable PathFragment where+  ppr (PositionalIndex i) = text "_" <> ppr i+  ppr (LabeledField n)    = ppr n++-- | >>> unconsTermKey (FromPath (FromPath (FromId hi) (Pos 1)) (Pos 2))+-- (hi, [1, 2])+unconsTermKey :: TermKey -> (Id, [PathFragment])+unconsTermKey = go [] where+  go acc (FromId i) = (i, reverse acc)+  go acc (FromPath k p) = go (p:acc) k
+ ghc-debugger/GHC/Debugger/Stopped.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,+   DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,+   TypeApplications, ScopedTypeVariables, BangPatterns #-}+module GHC.Debugger.Stopped where++import Data.IORef+import Control.Monad.Reader+import Control.Monad.IO.Class++import GHC+import GHC.Types.Unique.FM+#if MIN_VERSION_ghc(9,13,20250417)+import GHC.Types.Name.Occurrence (sizeOccEnv)+#endif+import GHC.Types.Name.Reader+import GHC.Unit.Home.ModInfo+import GHC.Unit.Module.ModDetails+import GHC.Types.TypeEnv+import GHC.Data.Maybe (expectJust)+import GHC.Driver.Env as GHC+import GHC.Runtime.Debugger.Breakpoints as GHC+import GHC.Runtime.Eval+import GHC.Types.SrcLoc+import qualified GHC.Runtime.Heap.Inspect as GHCI+import qualified GHC.Unit.Home.Graph as HUG++import GHC.Debugger.Stopped.Variables+import GHC.Debugger.Runtime+import GHC.Debugger.Runtime.Term.Cache+import GHC.Debugger.Monad+import GHC.Debugger.Interface.Messages+import GHC.Debugger.Utils++{-+Note [Don't crash if not stopped]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Requests such as `stacktrace`, `scopes`, or `variables` may end up+coming after the execution of a program has terminated. For instance,+consider this interleaving:++1. SENT Stopped event         <-- we're stopped+2. RECEIVED StackTrace req    <-- client issues after stopped event+3. RECEIVED Next req          <-- user clicks step-next+4. <program execution resumes and fails>+5. SENT Terminate event       <-- execution failed and we report it to exit cleanly+6. RECEIVED Scopes req        <-- happens as a sequence of 2 that wasn't canceled+7. <used to crash! because we're no longer at a breakpoint>++Now, we simply returned empty responses when these requests come in+while we're no longer at a breakpoint. The client will soon come to a halt+because of the termination event we sent.+-}++--------------------------------------------------------------------------------+-- * Stack trace+--------------------------------------------------------------------------------++-- | Get the stack frames at the point we're stopped at+getStacktrace :: Debugger [StackFrame]+getStacktrace = GHC.getResumeContext >>= \case+  [] ->+    -- See Note [Don't crash if not stopped]+    return []+  r:_+    | Just ss <- srcSpanToRealSrcSpan (GHC.resumeSpan r)+    -> return+        [ StackFrame+          { name = GHC.resumeDecl r+          , sourceSpan = realSrcSpanToSourceSpan ss+          }+        ]+    | otherwise ->+        -- No resume span; which should mean we're stopped on an exception.+        -- No info for now.+        return []++--------------------------------------------------------------------------------+-- * Scopes+--------------------------------------------------------------------------------++-- | Get the stack frames at the point we're stopped at+getScopes :: Debugger [ScopeInfo]+getScopes = GHC.getCurrentBreakSpan >>= \case+  Nothing ->+    -- See Note [Don't crash if not stopped]+    return []+  Just span'+    | Just rss <- srcSpanToRealSrcSpan span'+    , let sourceSpan = realSrcSpanToSourceSpan rss+    -> do+      -- It is /very important/ to report a number of variables (numVars) for+      -- larger scopes. If we just say "Nothing", then all variables of all+      -- scopes will be fetched at every stopped event.+      curr_modl <- expectJust <$> getCurrentBreakModule+      in_mod <- getTopEnv curr_modl+      imported <- getTopImported curr_modl+      return+        [ ScopeInfo { kind = LocalVariablesScope+                    , expensive = False+                    , numVars = Nothing+                    , sourceSpan+                    }+        , ScopeInfo { kind = ModuleVariablesScope+                    , expensive = True+                    , numVars = Just (sizeUFM in_mod)+                    , sourceSpan+                    }+        , ScopeInfo { kind = GlobalVariablesScope+                    , expensive = True+#if MIN_VERSION_ghc(9,13,20250417)+                    , numVars = Just (sizeOccEnv imported)+#else+                    , numVars = Nothing+#endif+                    , sourceSpan+                    }+        ]+    | otherwise ->+      -- No resume span; which should mean we're stopped on an exception+      -- TODO: Use exception context to create source span, or at least+      -- return the source span null to have Scopes at least.+      return []++--------------------------------------------------------------------------------+-- * Variables+--------------------------------------------------------------------------------+-- Note [Variables Requests]+-- ~~~~~~~~~~~~~~~~~~~~~~~~~+-- We can receive a Variables request for three different reasons+--+-- 1. To get the variables in a certain scope+-- 2. To inspect the value of a lazy variable+-- 3. To expand the structure of a variable+--+-- The replies are, respectively:+--+-- (VARR)+-- (a) All the variables in the request scope+-- (b) ONLY the variable requested+-- (c) The fields of the variable requested but NOT the original variable++-- | Get variables using a variable/variables reference+--+-- If the Variable Request ends up being case (VARR)(b), then we signal the+-- request forced the variable and return @Left varInfo@. Otherwise, @Right vis@.+--+-- See Note [Variables Requests]+getVariables :: VariableReference -> Debugger (Either VarInfo [VarInfo])+getVariables vk = do+  hsc_env <- getSession+  GHC.getResumeContext >>= \case+    [] ->+      -- See Note [Don't crash if not stopped]+      return (Right [])+    r:_ -> case vk of++      -- Only `seq` the variable when inspecting a specific one (`SpecificVariable`)+      -- (VARR)(b,c)+      SpecificVariable i -> do+        lookupVarByReference i >>= \case+          Nothing -> do+            -- lookupVarByReference failed.+            -- This may happen if, in a race, we change scope while asking for+            -- variables of the previous scope.+            -- Somewhat similar to the race in Note [Don't crash if not stopped]+            return (Right [])+          Just key -> do+            term <- obtainTerm key++            case term of++              -- (VARR)(b)+              Suspension{} -> do++                -- Original Term was a suspension:+                -- It is a "lazy" DAP variable: our reply can ONLY include+                -- this single variable.++                let ty = GHCI.termType term+                term' <- if isBoringTy ty+                            then deepseqTerm term -- deepseq boring types like String, because it is more helpful to print them whole than their structure.+                            else seqTerm term+                -- update cache with the forced term right away instead of invalidating it.+                -- TODO: is this the best place to have this update? what's the better abstraction?+                asks termCache >>= \r -> liftIO $ modifyIORef' r (insertTermCache key term')+                vi <- termToVarInfo key term'++                return (Left vi)++              -- (VARR)(c)+              _ -> Right <$> do++                -- Original Term was already something other than a Suspension;+                -- Meaning the @SpecificVariable@ request means to inspect the structure.+                -- Return ONLY the fields++                termVarFields key term >>= \case+                  NoFields -> return []+                  LabeledFields xs -> return xs+                  IndexedFields xs -> return xs+++      -- (VARR)(a) from here onwards++      LocalVariables -> fmap Right $+        -- bindLocalsAtBreakpoint hsc_env (GHC.resumeApStack r) (GHC.resumeSpan r) (GHC.resumeBreakpointId r)+        mapM tyThingToVarInfo =<< GHC.getBindings++      ModuleVariables -> Right <$> do+        case ibi_tick_mod <$> GHC.resumeBreakpointId r of+          Nothing -> return []+          Just curr_modl -> do+            things <- typeEnvElts <$> getTopEnv curr_modl+            mapM (\tt -> do+              nameStr <- display (getName tt)+              vi <- tyThingToVarInfo tt+              return vi{varName = nameStr}) things++      GlobalVariables -> Right <$> do+        case ibi_tick_mod <$> GHC.resumeBreakpointId r of+          Nothing -> return []+          Just curr_modl -> do+            names <- map greName . globalRdrEnvElts <$> getTopImported curr_modl+            mapM (\n-> do+              nameStr <- display n+              liftIO (GHC.lookupType hsc_env n) >>= \case+                Nothing ->+                  return VarInfo+                    { varName = nameStr+                    , varType = ""+                    , varValue = ""+                    , isThunk = False+                    , varRef = NoVariables+                    }+                Just tt -> do+                  vi <- tyThingToVarInfo tt+                  return vi{varName = nameStr}+              ) names++      NoVariables -> Right <$> do+        return []++--------------------------------------------------------------------------------+-- Inspect+--------------------------------------------------------------------------------++-- | All top-level things from a module, including unexported ones.+getTopEnv :: Module -> Debugger TypeEnv+getTopEnv modl = do+  hsc_env <- getSession+  liftIO $ HUG.lookupHugByModule modl (hsc_HUG hsc_env) >>= \case+    Nothing -> return emptyTypeEnv+    Just HomeModInfo+      { hm_details = ModDetails+        { md_types = things+        }+      } -> return things++-- | All bindings imported at a given module+getTopImported :: Module -> Debugger GlobalRdrEnv+getTopImported modl = do+  hsc_env <- getSession+  liftIO $ HUG.lookupHugByModule modl (hsc_HUG hsc_env) >>= \case+    Nothing -> return emptyGlobalRdrEnv+#if MIN_VERSION_ghc(9,13,20250417)+    Just hmi -> mkTopLevImportedEnv hsc_env hmi+#else+    Just hmi -> return emptyGlobalRdrEnv+#endif+
+ ghc-debugger/GHC/Debugger/Stopped/Variables.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,+   DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,+   TypeApplications, ScopedTypeVariables, BangPatterns #-}+module GHC.Debugger.Stopped.Variables where++import Control.Monad++import GHC+import GHC.Types.FieldLabel+import GHC.Runtime.Eval+import GHC.Core.DataCon+import GHC.Types.Id as GHC+import qualified GHC.Runtime.Debugger as GHCD+import qualified GHC.Runtime.Heap.Inspect as GHCI++import GHC.Debugger.Monad+import GHC.Debugger.Interface.Messages+import GHC.Debugger.Runtime+import GHC.Debugger.Runtime.Term.Key+import GHC.Debugger.Utils++-- | 'TyThing' to 'VarInfo'. The 'Bool' argument indicates whether to force the+-- value of the thing (as in @True = :force@, @False = :print@)+tyThingToVarInfo :: TyThing -> Debugger VarInfo+tyThingToVarInfo = \case+  t@(AConLike c) -> VarInfo <$> display c <*> display t <*> display t <*> pure False <*> pure NoVariables+  t@(ATyCon c)   -> VarInfo <$> display c <*> display t <*> display t <*> pure False <*> pure NoVariables+  t@(ACoAxiom c) -> VarInfo <$> display c <*> display t <*> display t <*> pure False <*> pure NoVariables+  AnId i -> do+    let key = FromId i+    term <- obtainTerm key+    termToVarInfo key term++-- | Construct the VarInfos of the fields ('VarFields') of the given 'TermKey'/'Term'+termVarFields :: TermKey -> Term -> Debugger VarFields+termVarFields top_key top_term =++  -- Make 'VarInfo's for the first layer of subTerms only.+  case top_term of+      -- Boring types don't get subfields+      _ | isBoringTy (GHCI.termType top_term) ->+        return NoFields++      Term{dc=Right dc, subTerms=_{- don't use directly! go through @obtainTerm@ -}} -> do+        case dataConFieldLabels dc of+          -- Not a record type,+          -- Use indexed fields+          [] -> do+            let keys = zipWith (\ix _ -> FromPath top_key (PositionalIndex ix)) [1..] (dataConOrigArgTys dc)+            IndexedFields <$> mapM (\k -> obtainTerm k >>= termToVarInfo k) keys+          -- Is a record data con,+          -- Use field labels+          dataConFields -> do+            let keys = map (FromPath top_key . LabeledField . flSelector) dataConFields+            LabeledFields <$> mapM (\k -> obtainTerm k >>= termToVarInfo k) keys+      NewtypeWrap{dc=Right dc, wrapped_term=_{- don't use directly! go through @obtainTerm@ -}} -> do+        case dataConFieldLabels dc of+          [] -> do+            let key = FromPath top_key (PositionalIndex 1)+            wvi <- obtainTerm key >>= termToVarInfo key+            return (IndexedFields [wvi])+          [fld] -> do+            let key = FromPath top_key (LabeledField (flSelector fld))+            wvi <- obtainTerm key >>= termToVarInfo key+            return (LabeledFields [wvi])+          _ -> error "unexpected number of Newtype fields: larger than 1"+      _ -> return NoFields+++-- | Construct a 'VarInfo' from the given 'Name' of the variable and the 'Term' it binds+termToVarInfo :: TermKey -> Term -> Debugger VarInfo+termToVarInfo key term = do+  -- Make a VarInfo for a term+  let+    isThunk+     | Suspension{} <- term = True+     | otherwise = False+    ty = GHCI.termType term++    -- We scrape the subterms to display as the var's value. The structure is+    -- displayed in the editor itself by expanding the variable sub-fields+    termHead t+      -- But show strings and lits in full+      | isBoringTy ty = t+      | otherwise     = case t of+         Term{}                    -> t{subTerms = []}+         NewtypeWrap{wrapped_term} -> t{wrapped_term = termHead wrapped_term}+         _                         -> t+  varName <- display key+  varType <- display ty+  varValue <- display =<< GHCD.showTerm (termHead term)+  -- liftIO $ print (varName, varType, varValue, GHCI.isFullyEvaluatedTerm term)++  -- The VarReference allows user to expand variable structure and inspect its value.+  -- Here, we do not want to allow expanding a term that is fully evaluated.+  -- We only want to return @SpecificVariable@ (which allows expansion) for+  -- values with sub-fields or thunks.+  varRef <- do+    if GHCI.isFullyEvaluatedTerm term+       -- Even if it is already evaluated, we do want to display a+       -- structure as long if it is not a "boring type" (one that does not+       -- provide useful information from being expanded)+       -- (e.g. consider how awkward it is to expand Char# 10 and I# 20)+       && (isBoringTy ty || not (hasDirectSubTerms term))+     then do+        return NoVariables+     else do+        ir <- getVarReference key+        return (SpecificVariable ir)++  return VarInfo{..}+  where+    hasDirectSubTerms = \case+      Suspension{}   -> False+      Prim{}         -> False+      NewtypeWrap{}  -> True+      RefWrap{}      -> True+      Term{subTerms} -> not $ null subTerms+
+ ghc-debugger/GHC/Debugger/Utils.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,+   DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,+   TypeApplications, ScopedTypeVariables, BangPatterns #-}+module GHC.Debugger.Utils where++import GHC+import GHC.Data.FastString+import GHC.Driver.DynFlags as GHC+import GHC.Driver.Ppr as GHC+import GHC.Utils.Outputable as GHC++import GHC.Debugger.Monad+import GHC.Debugger.Interface.Messages++--------------------------------------------------------------------------------+-- * GHC Utilities+--------------------------------------------------------------------------------++-- | Convert a GHC's src span into an interface one+realSrcSpanToSourceSpan :: RealSrcSpan -> SourceSpan+realSrcSpanToSourceSpan ss = SourceSpan+  { file = unpackFS $ srcSpanFile ss+  , startLine = srcSpanStartLine ss+  , startCol = srcSpanStartCol ss+  , endLine = srcSpanEndLine ss+  , endCol = srcSpanEndCol ss+  }++-- | Display an Outputable value as a String+display :: Outputable a => a -> Debugger String+display x = do+  dflags <- getDynFlags+  return $ showSDoc dflags (ppr x)+{-# INLINE display #-}+