packages feed

haskell-debugger 0.11.0.0 → 0.12.0.0

raw patch · 45 files changed

+1896/−1090 lines, 45 filesdep +ghc-experimentaldep −prettyprinterdep ~asyncdep ~dapdep ~ghciPVP ok

version bump matches the API change (PVP)

Dependencies added: ghc-experimental

Dependencies removed: prettyprinter

Dependency ranges changed: async, dap, ghci, time

API changes (from Hackage documentation)

- GHC.Debugger: EvalLog :: EvalLog -> DebuggerLog
- GHC.Debugger: data DebuggerLog
- GHC.Debugger: instance Prettyprinter.Internal.Pretty GHC.Debugger.DebuggerLog
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Classes.Eq GHC.Debugger.Interface.Messages.VarFields
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Classes.Eq GHC.Debugger.Interface.Messages.VarInfo
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Classes.Eq GHC.Debugger.Interface.Messages.VariableReference
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Classes.Ord GHC.Debugger.Interface.Messages.VariableReference
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Enum.Bounded GHC.Debugger.Interface.Messages.VariableReference
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Enum.Enum GHC.Debugger.Interface.Messages.VariableReference
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.Command
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.EvalResult
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.Response
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.VarFields
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.VarInfo
- GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.VariableReference
- GHC.Debugger.Logger: ($dmpretty) :: (Pretty a, Show a) => a -> Doc ann
- GHC.Debugger.Logger: Debug :: Severity
- GHC.Debugger.Logger: Error :: Severity
- GHC.Debugger.Logger: Info :: Severity
- GHC.Debugger.Logger: LogAction :: (msg -> m ()) -> LogAction (m :: Type -> Type) msg
- GHC.Debugger.Logger: Verbosity :: Severity -> Verbosity
- GHC.Debugger.Logger: Warning :: Severity
- GHC.Debugger.Logger: WithSeverity :: msg -> Severity -> WithSeverity msg
- GHC.Debugger.Logger: [getMsg] :: WithSeverity msg -> msg
- GHC.Debugger.Logger: [getSeverity] :: WithSeverity msg -> Severity
- GHC.Debugger.Logger: [threshold] :: Verbosity -> Severity
- GHC.Debugger.Logger: [unLogAction] :: LogAction (m :: Type -> Type) msg -> msg -> m ()
- GHC.Debugger.Logger: applyVerbosity :: Verbosity -> Recorder (WithSeverity a) -> Recorder (WithSeverity a)
- GHC.Debugger.Logger: class Contravariant (f :: Type -> Type)
- GHC.Debugger.Logger: class Pretty a
- GHC.Debugger.Logger: cmap :: (a -> b) -> Recorder b -> Recorder a
- GHC.Debugger.Logger: cmapIO :: (a -> IO b) -> Recorder b -> Recorder a
- GHC.Debugger.Logger: cmapWithSev :: (a -> b) -> Recorder (WithSeverity b) -> Recorder (WithSeverity a)
- GHC.Debugger.Logger: contramap :: Contravariant f => (a' -> a) -> f a -> f a'
- GHC.Debugger.Logger: data Recorder msg
- GHC.Debugger.Logger: data Severity
- GHC.Debugger.Logger: data WithSeverity msg
- GHC.Debugger.Logger: fromCologAction :: HasCallStack => LogAction IO msg -> Recorder msg
- GHC.Debugger.Logger: instance Data.Functor.Contravariant.Contravariant GHC.Debugger.Logger.Recorder
- GHC.Debugger.Logger: instance GHC.Internal.Base.Monoid (GHC.Debugger.Logger.Recorder msg)
- GHC.Debugger.Logger: instance GHC.Internal.Base.Semigroup (GHC.Debugger.Logger.Recorder msg)
- GHC.Debugger.Logger: logWith :: (HasCallStack, MonadIO m) => Recorder (WithSeverity msg) -> Severity -> msg -> m ()
- GHC.Debugger.Logger: newtype LogAction (m :: Type -> Type) msg
- GHC.Debugger.Logger: newtype Verbosity
- GHC.Debugger.Logger: pretty :: Pretty a => a -> Doc ann
- GHC.Debugger.Logger: prettyList :: Pretty a => [a] -> Doc ann
- GHC.Debugger.Logger: renderPretty :: Pretty a => a -> Text
- GHC.Debugger.Logger: renderPrettyWithSeverity :: Pretty a => WithSeverity a -> Text
- GHC.Debugger.Logger: renderSeverity :: Severity -> Text
- GHC.Debugger.Logger: renderWithSeverity :: (a -> Text) -> WithSeverity a -> Text
- GHC.Debugger.Logger: renderWithTimestamp :: Text -> IO Text
- GHC.Debugger.Logger: toCologAction :: forall (m :: Type -> Type) msg. (MonadIO m, HasCallStack) => Recorder msg -> LogAction m msg
- GHC.Debugger.Monad: [genUniq] :: DebuggerState -> IORef Int
- GHC.Debugger.Monad: [varReferences] :: DebuggerState -> IORef (IntMap TermKey, TermKeyMap Int)
- GHC.Debugger.Monad: data DebuggerMonadLog
- GHC.Debugger.Monad: debuggerLoggerAction :: Handle -> LogAction
- GHC.Debugger.Monad: freshInt :: Debugger Int
- GHC.Debugger.Monad: getVarReference :: TermKey -> Debugger Int
- GHC.Debugger.Monad: ghcSevSeverity :: Severity -> Severity
- GHC.Debugger.Monad: instance Prettyprinter.Internal.Pretty GHC.Debugger.Monad.DebuggerMonadLog
- GHC.Debugger.Monad: leaveSuspendedState :: Debugger ()
- GHC.Debugger.Monad: logAction :: Recorder (WithSeverity DebuggerMonadLog) -> DynFlags -> LogAction
- GHC.Debugger.Monad: lookupVarByReference :: Int -> Debugger (Maybe TermKey)
- GHC.Debugger.Run: LogEvalModule :: Module -> EvalLog
- GHC.Debugger.Run: data EvalLog
- GHC.Debugger.Run: instance Prettyprinter.Internal.Pretty GHC.Debugger.Run.EvalLog
- GHC.Debugger.Runtime.Term.Cache: insertTermKeyMap :: TermKey -> a -> TermKeyMap a -> TermKeyMap a
- GHC.Debugger.Runtime.Term.Cache: lookupTermKeyMap :: TermKey -> TermKeyMap a -> Maybe a
- GHC.Debugger.Runtime.Term.Cache: type TermKeyMap a = IdEnv Map [PathFragment 'True] a
- GHC.Debugger.Runtime.Term.Key: [CustomField] :: String -> PathFragment 'True
- GHC.Debugger.Runtime.Term.Key: unconsTermKey :: TermKey -> (Id, [PathFragment 'True])
- GHC.Debugger.Runtime.Thread.Stack: getRemoteThreadIPEStack :: ForeignRef ThreadId -> Debugger [StackEntry]
- GHC.Debugger.Utils: expectRight :: Exception e => Either e a -> Debugger a
+ GHC.Debugger.Interface.Messages: ExceptionInfo :: String -> String -> String -> Maybe String -> [ExceptionInfo] -> ExceptionInfo
+ GHC.Debugger.Interface.Messages: ForcedVariable :: VarInfo -> VariableResult
+ GHC.Debugger.Interface.Messages: GetExceptionInfo :: RemoteThreadId -> Command
+ GHC.Debugger.Interface.Messages: GotExceptionInfo :: ExceptionInfo -> Response
+ GHC.Debugger.Interface.Messages: VariableFields :: [VarInfo] -> VariableResult
+ GHC.Debugger.Interface.Messages: [exceptionInfoContext] :: ExceptionInfo -> Maybe String
+ GHC.Debugger.Interface.Messages: [exceptionInfoFullTypeName] :: ExceptionInfo -> String
+ GHC.Debugger.Interface.Messages: [exceptionInfoInner] :: ExceptionInfo -> [ExceptionInfo]
+ GHC.Debugger.Interface.Messages: [exceptionInfoMessage] :: ExceptionInfo -> String
+ GHC.Debugger.Interface.Messages: [exceptionInfoTypeName] :: ExceptionInfo -> String
+ GHC.Debugger.Interface.Messages: data ExceptionInfo
+ GHC.Debugger.Interface.Messages: data VariableResult
+ GHC.Debugger.Interface.Messages: instance GHC.Internal.Classes.Eq GHC.Debugger.Interface.Messages.SourceSpan
+ GHC.Debugger.Interface.Messages: instance GHC.Internal.Show.Show GHC.Debugger.Interface.Messages.ExceptionInfo
+ GHC.Debugger.Interface.Messages: unhelpfulSourceSpan :: SourceSpan
+ GHC.Debugger.Interface.Messages: variableResultToList :: VariableResult -> [VarInfo]
+ GHC.Debugger.Monad: DebuggerLog :: Severity -> DebuggerMessage -> DebuggerLog
+ GHC.Debugger.Monad: GHCLog :: LogFlags -> MessageClass -> SrcSpan -> SDoc -> DebuggerLog
+ GHC.Debugger.Monad: LogDebuggeeErr :: Text -> DebuggerLog
+ GHC.Debugger.Monad: LogDebuggeeOut :: Text -> DebuggerLog
+ GHC.Debugger.Monad: [externalInterpreterStdinStream] :: RunDebuggerSettings -> StdStream
+ GHC.Debugger.Monad: [preferInternalInterpreter] :: RunDebuggerSettings -> Bool
+ GHC.Debugger.Monad: data DebuggerLog
+ GHC.Debugger.Monad: data DebuggerMessage
+ GHC.Debugger.Monad: expectRight :: Exception e => Either e a -> Debugger a
+ GHC.Debugger.Monad: ghcLogAction :: LogAction IO DebuggerLog -> LogAction
+ GHC.Debugger.Monad: instance GHC.Internal.Show.Show GHC.Debugger.Monad.DebuggerMessage
+ GHC.Debugger.Runtime.Eval: EvalApp :: EvalExpr a -> EvalExpr a -> EvalExpr a
+ GHC.Debugger.Runtime.Eval: EvalThis :: a -> EvalExpr a
+ GHC.Debugger.Runtime.Eval: data EvalExpr a
+ GHC.Debugger.Runtime.Eval: evalString :: ForeignRef (IO String) -> Debugger String
+ GHC.Debugger.Runtime.Eval.RemoteExpr.Builtin: decodeStackWithIpe :: RemoteExpr StackSnapshot -> RemoteExpr (IO [(StackFrame, Maybe InfoProv)])
+ GHC.Debugger.Runtime.Eval.RemoteExpr.Builtin: displayStackAnnotation :: RemoteExpr SomeStackAnnotation -> RemoteExpr String
+ GHC.Debugger.Runtime.Thread.Stack: StackFrameAnnotation :: Maybe SrcLoc -> String -> StackFrameInfo
+ GHC.Debugger.Runtime.Thread.Stack: StackFrameBreakpointInfo :: InternalBreakpointId -> StackFrameInfo
+ GHC.Debugger.Runtime.Thread.Stack: StackFrameIPEInfo :: InfoProv -> StackFrameInfo
+ GHC.Debugger.Runtime.Thread.Stack: data StackFrameInfo
+ GHC.Debugger.Session: addOptI :: String -> DynFlags -> DynFlags
+ GHC.Debugger.Session: enableDynamicDebuggee :: DynFlags -> DynFlags
+ GHC.Debugger.Session: enableExternalInterpreter :: Bool -> DynFlags -> DynFlags
+ GHC.Debugger.Session: setDynFlagWays :: Ways -> DynFlags -> DynFlags
+ GHC.Debugger.Session: setPgmI :: String -> DynFlags -> DynFlags
+ GHC.Debugger.Session: validateUnitsWays :: NonEmpty (DynFlags, [Target]) -> IO Ways
+ GHC.Debugger.Stopped: srcLocToSourceSpan :: SrcLoc -> SourceSpan
+ GHC.Debugger.Stopped.Exception: currentlyStoppedOnException :: Debugger Bool
+ GHC.Debugger.Stopped.Exception: defaultExceptionInfo :: ExceptionInfo
+ GHC.Debugger.Stopped.Exception: exceptionSourceSpanFromContext :: Debugger (Maybe SourceSpan)
+ GHC.Debugger.Stopped.Exception: fallbackExceptionSourceSpan :: Maybe SrcSpan -> SourceSpan
+ GHC.Debugger.Stopped.Exception: getExceptionInfo :: RemoteThreadId -> Debugger ExceptionInfo
+ GHC.Debugger.Utils: forwardHandleToLogger :: Handle -> LogAction IO Text -> IO ()
+ GHC.Debugger.Utils: showSDoc :: DynFlags -> SDoc -> String
- GHC.Debugger: execute :: Recorder (WithSeverity DebuggerLog) -> Command -> Debugger Response
+ GHC.Debugger: execute :: Command -> Debugger Response
- GHC.Debugger.Interface.Messages: DbgStackFrame :: String -> SourceSpan -> DbgStackFrame
+ GHC.Debugger.Interface.Messages: DbgStackFrame :: String -> SourceSpan -> Maybe InternalBreakpointId -> DbgStackFrame
- GHC.Debugger.Interface.Messages: GetScopes :: Command
+ GHC.Debugger.Interface.Messages: GetScopes :: RemoteThreadId -> Int -> Command
- GHC.Debugger.Interface.Messages: GetVariables :: VariableReference -> Command
+ GHC.Debugger.Interface.Messages: GetVariables :: RemoteThreadId -> Int -> VariableReference -> Command
- GHC.Debugger.Interface.Messages: GotVariables :: Either VarInfo [VarInfo] -> Response
+ GHC.Debugger.Interface.Messages: GotVariables :: VariableResult -> Response
- GHC.Debugger.Interface.Messages: SpecificVariable :: Int -> VariableReference
+ GHC.Debugger.Interface.Messages: SpecificVariable :: TermKey -> VariableReference
- GHC.Debugger.Interface.Messages: [breakId] :: EvalResult -> Maybe InternalBreakpointId
+ GHC.Debugger.Interface.Messages: [breakId] :: DbgStackFrame -> Maybe InternalBreakpointId
- GHC.Debugger.Monad: DebuggerState :: IORef (BreakpointMap (BreakpointStatus, BreakpointKind)) -> IORef (IntMap TermKey, TermKeyMap Int) -> IORef RuntimeInstancesCache -> IORef ThreadMap -> IORef CompCache -> IORef Int -> Maybe UnitId -> Recorder (WithSeverity DebuggerMonadLog) -> DebuggerState
+ GHC.Debugger.Monad: DebuggerState :: IORef (BreakpointMap (BreakpointStatus, BreakpointKind)) -> IORef RuntimeInstancesCache -> IORef ThreadMap -> IORef CompCache -> Maybe UnitId -> LogAction Debugger DebuggerLog -> DebuggerState
- GHC.Debugger.Monad: LogFailedToCompileDebugViewModule :: ModuleName -> DebuggerMonadLog
+ GHC.Debugger.Monad: LogFailedToCompileDebugViewModule :: ModuleName -> DebuggerMessage
- GHC.Debugger.Monad: LogSDoc :: DynFlags -> SDoc -> DebuggerMonadLog
+ GHC.Debugger.Monad: LogSDoc :: DynFlags -> SDoc -> DebuggerMessage
- GHC.Debugger.Monad: LogSkippingViewModuleNoPkg :: ModuleName -> String -> [String] -> DebuggerMonadLog
+ GHC.Debugger.Monad: LogSkippingViewModuleNoPkg :: ModuleName -> String -> [String] -> DebuggerMessage
- GHC.Debugger.Monad: RunDebuggerSettings :: Bool -> Bool -> RunDebuggerSettings
+ GHC.Debugger.Monad: RunDebuggerSettings :: Bool -> Bool -> Bool -> StdStream -> RunDebuggerSettings
- GHC.Debugger.Monad: [dbgLogger] :: DebuggerState -> Recorder (WithSeverity DebuggerMonadLog)
+ GHC.Debugger.Monad: [dbgLogger] :: DebuggerState -> LogAction Debugger DebuggerLog
- GHC.Debugger.Monad: initialDebuggerState :: Recorder (WithSeverity DebuggerMonadLog) -> Maybe UnitId -> Ghc DebuggerState
+ GHC.Debugger.Monad: initialDebuggerState :: LogAction Debugger DebuggerLog -> Maybe UnitId -> Ghc DebuggerState
- GHC.Debugger.Monad: runDebugger :: Recorder (WithSeverity DebuggerMonadLog) -> Handle -> FilePath -> FilePath -> FilePath -> [String] -> [String] -> [String] -> FilePath -> RunDebuggerSettings -> Debugger a -> IO a
+ GHC.Debugger.Monad: runDebugger :: LogAction IO DebuggerLog -> FilePath -> FilePath -> FilePath -> [String] -> [String] -> [String] -> FilePath -> RunDebuggerSettings -> Debugger a -> IO a
- GHC.Debugger.Monad: tryLoadHsDebuggerViewModule :: GhcMonad m => Recorder (WithSeverity DebuggerMonadLog) -> Maybe ModIfaceCache -> (Target -> Bool) -> ModuleName -> StringBuffer -> m SuccessFlag
+ GHC.Debugger.Monad: tryLoadHsDebuggerViewModule :: GhcMonad m => LogAction IO DebuggerLog -> Maybe ModIfaceCache -> (Target -> Bool) -> ModuleName -> StringBuffer -> m SuccessFlag
- GHC.Debugger.Run: debugExecution :: Recorder (WithSeverity EvalLog) -> FilePath -> EntryPoint -> [String] -> Debugger EvalResult
+ GHC.Debugger.Run: debugExecution :: FilePath -> EntryPoint -> [String] -> Debugger EvalResult
- GHC.Debugger.Runtime.Term.Key: [LabeledField] :: forall (b :: Bool). Name -> PathFragment b
+ GHC.Debugger.Runtime.Term.Key: [LabeledField] :: forall (b :: Bool). Int -> Name -> PathFragment b
- GHC.Debugger.Runtime.Thread.Stack: getRemoteThreadStackCopy :: ForeignRef ThreadId -> Debugger [InternalBreakpointId]
+ GHC.Debugger.Runtime.Thread.Stack: getRemoteThreadStackCopy :: ForeignRef ThreadId -> Debugger [StackFrameInfo]
- GHC.Debugger.Stopped: getScopes :: Debugger [ScopeInfo]
+ GHC.Debugger.Stopped: getScopes :: RemoteThreadId -> Int -> Debugger [ScopeInfo]
- GHC.Debugger.Stopped: getVariables :: VariableReference -> Debugger (Either VarInfo [VarInfo])
+ GHC.Debugger.Stopped: getVariables :: RemoteThreadId -> Int -> VariableReference -> Debugger VariableResult
- GHC.Debugger.Utils: display :: Outputable a => a -> Debugger String
+ GHC.Debugger.Utils: display :: (GhcMonad m, Outputable a) => a -> m String

Files

CHANGELOG.md view
@@ -1,5 +1,21 @@ # Revision history for haskell-debugger +## 0.12.0.0 -- 2026-02-11++* Improved exceptions support!+    * Break-on-exception breakpoints now provide source locations+    * And exception callstacks based on the ExceptionAnnotation mechanism.+* Introduced stacktraces support!+    * Stack frames decoded from interpreter frames with breakpoints are displayed+    * Stack frames decoded from IPE information available for compiled code frames too+    * Custom stack annotations will also be displayed+* Use the external interpreter by default!+    * Paves the way for separating debugger threads vs debuggee threads in multi-threaded debugging+    * Allows debuggee vs debugger output to be separated by construction+* Windows is now supported when using the external interpreter (default)+* Fixed bug where existential constraints weren't displayed in the variables pane+* Plus more bug fixes, refactors, test improvements, and documentation updates.+ ## 0.11.0.0 -- 2026-01-05  * Introduce but don't yet expose infrastructure for debugging multi-threaded
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2025, Rodrigo Mesquita+Copyright (c) 2025, Well-Typed   Redistribution and use in source and binary forms, with or without
README.md view
@@ -10,7 +10,7 @@ [project homepage](https://well-typed.github.io/haskell-debugger/)!  > [!WARNING]-> `hdb` can currently be compiled with the 9.14 alpha pre-releases or with a nightly version+> `hdb` can currently be compiled with GHC 9.14 > The first release it will be compatible with is GHC 9.14.  To install and use the debugger, you need the executable `hdb`@@ -47,6 +47,9 @@  Change them accordingly. +You can also run the debugger as a server on a specific port by executing `hdb server --port 12345`.+To connect VSCode to this server, add the field `"debugServer": 12345` to your `launch.json` configuration.+ To run the debugger, simply hit the green run button. See the Features section below for what is currently supported. @@ -76,7 +79,7 @@  We have been doing custom work on GHC to support debugging in a predictable, robust, and more performant way. That is why `hdb` is only-compatible with the latest and greatest GHC. If you want to debug using an older+compatible with GHC 9.14. If you want to debug using an older GHC version (9.12 and older), please check out `haskell-debug-adapter`.  To implement the Debug Adapter Protocol (DAP) server part, we are using the@@ -149,13 +152,29 @@  # Building from source -To build `hdb`:+## Build `hdb` (using Cabal)+ ```-cabal build -w /path/to/recent/ghc exe:hdb+cabal build -w /path/to/ghc-9.14 exe:hdb ``` -To build the VSCode extension+## Build and install `hdb` (using Stack)++On non-Windows operating systems:+ ```+stack install haskell-debugger+```++On Windows:++```+stack -w stack-windows.yaml install haskell-debugger+```++## Build VS Code extension (using Nix)++``` cd vscode-extension nix-build ```@@ -165,5 +184,5 @@ ``` cd test/integration-tests make GHC=/path/to/recent/ghc \-     DEBUGGER=$(cd ../.. && cabal list-bin -w /path/to/recent/ghc exe:hdb)+     DEBUGGER=$(cd ../.. && cabal list-bin -w /path/to/ghc-9.14 exe:hdb) ```
haskell-debugger-view/src/GHC/Debugger/View/Class.hs view
@@ -51,6 +51,7 @@  import Data.Int import Data.Word+import Control.Exception  -- | Custom handling of debug terms (e.g. in the variables pane, or when -- inspecting a lazy variable)@@ -75,7 +76,7 @@   debugFields :: a -> Program VarFields  -- | The 'Program' abstraction allows more complicated 'DebugView' instances--- to be constructed. The debugger will interpreter a 'Program' lazily when+-- to be constructed. The debugger will interpret a 'Program' lazily when -- determining how to display a variable. -- -- At the moment the only interesting query when constructing a program is determining@@ -180,6 +181,15 @@     [ ("fst", VarFieldValue x)     , ("snd", VarFieldValue y) ] +instance DebugView SomeException where+  debugValue e = simpleValue (displayException e) True+  debugFields e@(SomeException exc) =+    let !ctx = someExceptionContext e+    in pure $ VarFields+    [ ("exception", VarFieldValue exc)+    , ("context", VarFieldValue ctx)+    ]+ -- | This instance will display up to the first 50 forced elements of a list. instance {-# OVERLAPPABLE #-} DebugView [a] where   debugValue [] = simpleValue "[]" False@@ -219,4 +229,3 @@ toVarFieldsIO x =   case x of     VarFields fls -> [ (pure fl_s, b) | (fl_s, b) <- fls]-
haskell-debugger.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.12 name:               haskell-debugger-version:            0.11.0.0+version:            0.12.0.0 synopsis:     A step-through debugger for GHC Haskell @@ -58,9 +58,9 @@     exposed-modules:  GHC.Debugger,                       GHC.Debugger.Breakpoint,                       GHC.Debugger.Breakpoint.Map,-                      GHC.Debugger.Logger,                       GHC.Debugger.Run,                       GHC.Debugger.Stopped,+                      GHC.Debugger.Stopped.Exception,                       GHC.Debugger.Stopped.Variables,                        GHC.Debugger.Runtime,@@ -77,7 +77,6 @@                        GHC.Debugger.Runtime.Term.Parser,                       GHC.Debugger.Runtime.Term.Key,-                      GHC.Debugger.Runtime.Term.Cache,                        GHC.Debugger.Runtime.Thread,                       GHC.Debugger.Runtime.Thread.Stack,@@ -95,6 +94,7 @@                       ghc >= 9.14 && < 9.16, ghci >= 9.14 && < 9.16,                       ghc-boot-th >= 9.14 && < 9.16,                       ghc-boot >= 9.14 && < 9.16,+                      ghc-experimental >= 9.1401 && < 9.1600,                       ghc-heap >= 9.14 && < 9.16,                       array >= 0.5.8 && < 0.6,                       containers >= 0.7 && < 0.9,@@ -105,15 +105,14 @@                       directory >= 1.3.9.0 && < 1.4,                       exceptions >= 0.10.9 && < 0.11,                       bytestring >= 0.12.1 && < 0.13,+                      async >= 2.2.6 && < 2.3,                       cryptohash-sha1 >= 0.11.101.0 && < 0.12,                       base16-bytestring >= 1.0.2.0 && < 1.1,                       aeson >= 2.2.3 && < 2.3,                       hie-bios >= 0.15 && < 0.18,                       file-embed >= 0.0.16 && < 0.1,                       attoparsec >= 0.13 && < 0.15,-                      -- Logger dependencies                       time >= 1.14 && < 2,-                      prettyprinter >= 1.7.1 && < 2,                       text >= 2.1 && < 2.3,                       co-log-core >= 0.3.2.5 && < 0.4, @@ -132,6 +131,7 @@                       Development.Debug.Adapter.Stepping,                       Development.Debug.Adapter.Stopped,                       Development.Debug.Adapter.Evaluation,+                      Development.Debug.Adapter.ExceptionInfo,                       Development.Debug.Adapter.Init,                       Development.Debug.Adapter.Interface,                       Development.Debug.Adapter.Output,@@ -151,7 +151,7 @@                       Paths_haskell_debugger     autogen-modules:  Paths_haskell_debugger     build-depends:-        base, ghc,+        base, ghc, ghci,         exceptions, aeson, bytestring,         containers, filepath,         process, mtl,@@ -162,15 +162,14 @@         co-log-core >= 0.3.2.5 && < 0.4,         implicit-hie ^>=0.1.4.0,         transformers >= 0.6 && < 0.7,-        -- Logger dependencies-        prettyprinter,+        time,          directory >= 1.3.9 && < 1.4,         network >= 3.2.8,         network-run >= 0.4.4,         async >= 2.2.5 && < 2.3,         text >= 2.1 && < 2.3,-        dap >= 0.3.1 && < 0.4,+        dap >= 0.4 && < 0.5,          haskeline >= 0.8 && < 1,         optparse-applicative >= 0.18 && < 0.20@@ -178,6 +177,8 @@     default-language: GHC2021     default-extensions: CPP     ghc-options: -threaded+    -- See Note [Custom external interpreter]+    ghc-options: -fkeep-cafs  test-suite haskell-debugger-test     import:           warnings
haskell-debugger/GHC/Debugger.hs view
@@ -9,17 +9,17 @@ import GHC.Debugger.Breakpoint import GHC.Debugger.Run import GHC.Debugger.Stopped+import GHC.Debugger.Stopped.Exception (getExceptionInfo) import GHC.Debugger.Monad import GHC.Debugger.Interface.Messages-import GHC.Debugger.Logger  -------------------------------------------------------------------------------- -- * Executing commands --------------------------------------------------------------------------------  -- | Execute the given debugger command in the current 'Debugger' session-execute :: Recorder (WithSeverity DebuggerLog) -> Command -> Debugger Response-execute recorder = \case+execute :: Command -> Debugger Response+execute = \case   ClearFunctionBreakpoints -> DidClearBreakpoints <$ clearBreakpoints Nothing   ClearModBreakpoints fp -> DidClearBreakpoints <$ clearBreakpoints (Just fp)   SetBreakpoint{brk, hitCount, condition} ->@@ -28,22 +28,15 @@   GetBreakpointsAt bp -> DidGetBreakpoints <$> getBreakpointsAt bp   GetThreads -> GotThreads <$> getThreads   GetStacktrace i -> GotStacktrace <$> getStacktrace i-  GetScopes -> GotScopes <$> getScopes-  GetVariables kind -> GotVariables <$> getVariables kind+  GetScopes threadId frameIx -> GotScopes <$> getScopes threadId frameIx+  GetVariables threadId frameIx varRef -> GotVariables <$> getVariables threadId frameIx varRef+  GetExceptionInfo threadId -> GotExceptionInfo <$> getExceptionInfo threadId   DoEval exp_s -> DidEval <$> doEval exp_s   DoContinue -> DidContinue <$> doContinue   DoSingleStep -> DidStep <$> doSingleStep   DoStepOut -> DidStep <$> doStepOut   DoStepLocal -> DidStep <$> doLocalStep-  DebugExecution { entryPoint, entryFile, runArgs } -> DidExec <$> debugExecution (cmapWithSev EvalLog recorder) entryFile entryPoint runArgs+  DebugExecution { entryPoint, entryFile, runArgs } -> DidExec <$> debugExecution entryFile entryPoint runArgs   TerminateProcess -> liftIO $ do     -- Terminate!     exitWith ExitSuccess--data DebuggerLog-  = EvalLog EvalLog--instance Pretty DebuggerLog where-  pretty = \ case-    EvalLog msg -> pretty msg-
haskell-debugger/GHC/Debugger/Breakpoint.hs view
@@ -12,6 +12,7 @@ import Data.IORef import System.Directory import System.FilePath+import qualified Colog.Core as Logger  import GHC import GHC.ByteCode.Breakpoints@@ -28,7 +29,6 @@  import GHC.Debugger.Monad import GHC.Debugger.Session-import GHC.Debugger.Logger as Logger import GHC.Debugger.Utils import GHC.Debugger.Interface.Messages import qualified GHC.Debugger.Breakpoint.Map as BM
haskell-debugger/GHC/Debugger/Interface/Messages.hs view
@@ -1,6 +1,7 @@ {-# OPTIONS_GHC -Wno-orphans #-} -- TODO: drop this and Show GHC.InternalBreakpointId... {-# LANGUAGE LambdaCase,              StandaloneDeriving,+             DataKinds,              OverloadedStrings,              DuplicateRecordFields,              TypeApplications@@ -12,6 +13,8 @@ import qualified GHC import qualified GHC.Utils.Outputable as GHC +import GHC.Debugger.Runtime.Term.Key+ -------------------------------------------------------------------------------- -- Commands --------------------------------------------------------------------------------@@ -20,7 +23,7 @@ data Command    -- | Set a breakpoint on a given function, or module by line number-  = SetBreakpoint { brk       :: Breakpoint +  = SetBreakpoint { brk       :: Breakpoint                   , hitCount  :: Maybe Int                   -- ^ Stop after N hits (if @isJust condition@, count down only when @eval condition == True@)                   , condition :: Maybe String@@ -47,17 +50,20 @@   | GetStacktrace RemoteThreadId    -- | Get the list of available scopes at the current breakpoint-  | GetScopes+  | GetScopes RemoteThreadId Int    -- | Get the variables in scope for the current breakpoint.   --   -- Note: for GHCs <9.16 this only reports the variables free in the expression   -- we're stopped at rather than all variables in scope.-  | GetVariables VariableReference+  | GetVariables RemoteThreadId Int{-stack frame positional ix-} VariableReference    -- | Evaluate an expression at the current breakpoint.   | DoEval String +  -- | Get information about the current exception (if any) on a thread.+  | GetExceptionInfo RemoteThreadId+   -- | Continue executing from the current breakpoint   | DoContinue @@ -104,7 +110,6 @@   deriving (Show)  newtype VarFields = VarFields [VarInfo]-  deriving (Show, Eq)  -- | Information about a variable data VarInfo = VarInfo@@ -118,8 +123,23 @@       -- TODO:       --  memory reference using ghc-debug.       }-      deriving (Show, Eq) +-- | Result of requesting variables.+--+-- If the variable you requested is a thunk then `ForcedVariable` is returned, which+-- is the variable you requested but forced.+--+-- If the variable you requested is not a thunk, then 'VariableFields` is returned which+-- contains the subfields of the variable.+data VariableResult+  = ForcedVariable VarInfo+  | VariableFields [VarInfo]++variableResultToList :: VariableResult -> [VarInfo]+variableResultToList = \case+  ForcedVariable vi -> [vi]+  VariableFields vis -> vis+ -- | What kind of breakpoint are we referring to, module or function breakpoints? -- Used e.g. in the 'ClearBreakpoints' request data BreakpointKind@@ -154,35 +174,14 @@    -- | A reference to a specific variable.   -- Used to force its result or get its structured children-  | SpecificVariable Int--  deriving (Show, Eq, Ord)+  | SpecificVariable TermKey --- | From 'ScopeVariablesReference' to a 'VariableReference' that can be used in @"variable"@ requests scopeToVarRef :: ScopeVariablesReference -> VariableReference scopeToVarRef = \case   LocalVariablesScope -> LocalVariables   ModuleVariablesScope -> ModuleVariables   GlobalVariablesScope -> GlobalVariables --instance Bounded VariableReference where-  minBound = NoVariables-  maxBound = SpecificVariable maxBound--instance Enum VariableReference where-  toEnum 0 = NoVariables-  toEnum 1 = LocalVariables-  toEnum 2 = ModuleVariables-  toEnum 3 = GlobalVariables-  toEnum n = SpecificVariable (n - 4)--  fromEnum NoVariables          = 0-  fromEnum LocalVariables       = 1-  fromEnum ModuleVariables      = 2-  fromEnum GlobalVariables      = 3-  fromEnum (SpecificVariable n) = 4 + n- -- | A source span type for the interface. Like 'RealSrcSpan'. data SourceSpan = SourceSpan       { file :: !FilePath@@ -196,8 +195,22 @@       , endCol :: {-# UNPACK #-} !Int       -- ^ RealSrcSpan end col       }-      deriving (Show)+      deriving (Show, Eq) +-- | This is a completely unhelpful source span!+-- It doesn't point to anything and is of no help to the user+-- whatsoever.+--+-- Use this only as a last resort if no other source span can be provided.+unhelpfulSourceSpan :: SourceSpan+unhelpfulSourceSpan = SourceSpan+  { file = ""+  , startLine = 0+  , endLine = 0+  , startCol = 0+  , endCol = 0+  }+ -------------------------------------------------------------------------------- -- Responses --------------------------------------------------------------------------------@@ -215,7 +228,8 @@   | GotThreads [DebuggeeThread]   | GotStacktrace [DbgStackFrame]   | GotScopes [ScopeInfo]-  | GotVariables (Either VarInfo [VarInfo])+  | GotVariables VariableResult+  | GotExceptionInfo ExceptionInfo   | Aborted String   | Initialised @@ -255,7 +269,8 @@                   , resultStructureRef :: VariableReference                   -- ^ A structured representation of the result of evaluating                   -- the expression given as a "virtual" 'VariableReference'-                  -- that the user can expand as a normal variable.+                  -- that the user can use to refer to the result and inspect+                  -- interactively and expand it.                   }   | EvalException { resultVal :: String, resultType :: String }   | EvalStopped   { breakId :: Maybe GHC.InternalBreakpointId@@ -265,7 +280,6 @@                   }   -- | Evaluation failed for some reason other than completed/completed-with-exception/stopped.   | EvalAbortedWith String-  deriving (Show)  data DebuggeeThread   = DebuggeeThread@@ -282,16 +296,24 @@     -- ^ Title of stack frame     , sourceSpan :: SourceSpan     -- ^ Source span for this stack frame+    , breakId :: Maybe GHC.InternalBreakpointId+    -- ^ Is this a BCO continuation frame with a breakpoint?+    -- If yes, we can leverage the breakpoint info to report scopes.     }   deriving (Show) +data ExceptionInfo = ExceptionInfo+  { exceptionInfoTypeName     :: String+  , exceptionInfoFullTypeName :: String+  , exceptionInfoMessage      :: String+  , exceptionInfoContext      :: Maybe String+  , exceptionInfoInner        :: [ExceptionInfo]+  }+  deriving (Show)+ -------------------------------------------------------------------------------- -- Instances -------------------------------------------------------------------------------- -deriving instance Show Command-deriving instance Show Response- instance Show GHC.InternalBreakpointId where   show (GHC.InternalBreakpointId m ix) = "InternalBreakpointId " ++ GHC.showPprUnsafe m ++ " " ++ show ix-
− haskell-debugger/GHC/Debugger/Logger.hs
@@ -1,130 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE RankNTypes #-}---- | Simple Logger API using co-log style loggers-module GHC.Debugger.Logger (-  -- * The core Logger type-  Recorder,-  logWith,-  -- * Log messages-  Pretty(..),-  -- * For simpler usage-  Colog.LogAction (..),-  toCologAction,-  fromCologAction,-  -- * Severity-  Severity (..),-  WithSeverity (..),-  cmap,-  cmapIO,-  cmapWithSev,-  -- * Verbosity-  Verbosity(..),-  applyVerbosity,--  -- * Pretty printing of logs-  renderPrettyWithSeverity,-  renderWithSeverity,-  renderPretty,-  renderSeverity,-  renderWithTimestamp,--  -- Re-exports-  module Data.Functor.Contravariant,-) where--import GHC.Stack--import Control.Monad.IO.Class-import Control.Monad ((>=>))--import Colog.Core (Severity(..), WithSeverity(..), filterBySeverity)-import qualified Colog.Core as Colog-import Data.Functor.Contravariant (Contravariant (contramap))-import Data.Text (Text)-import qualified Data.Text as T-import Prettyprinter-import Prettyprinter.Render.Text (renderStrict)-import Data.Time (defaultTimeLocale, formatTime, getCurrentTime)--newtype Recorder msg = Recorder-  { logger_ :: forall m. (MonadIO m) => msg -> m () }--instance Contravariant Recorder where-  contramap f Recorder{ logger_ } =-    Recorder-      { logger_ = logger_ . f }--instance Semigroup (Recorder msg) where-  (<>) Recorder{ logger_ = logger_1 } Recorder{ logger_ = logger_2 } =-    Recorder-      { logger_ = \msg -> logger_1 msg >> logger_2 msg }--instance Monoid (Recorder msg) where-  mempty =-    Recorder-      { logger_ = \_ -> pure () }---- | Logging verbosity, where all Severities matching or exceeding the threshold are printed-newtype Verbosity = Verbosity { threshold :: Severity }---- | Make this logger never report messages whose severity is below the given verbosity severity.-applyVerbosity :: Verbosity -> Recorder (WithSeverity a) -> Recorder (WithSeverity a)-applyVerbosity Verbosity{threshold} rc-  = fromCologAction $ filterBySeverity threshold getSeverity (toCologAction rc)--logWith :: (HasCallStack, MonadIO m) => Recorder (WithSeverity msg) -> Severity -> msg -> m ()-logWith Recorder{logger_} sev msg = logger_ $ WithSeverity msg sev--cmap :: (a -> b) -> Recorder b -> Recorder a-cmap = contramap--cmapWithSev :: (a -> b) -> Recorder (WithSeverity b) -> Recorder (WithSeverity a)-cmapWithSev f = contramap (fmap f)--cmapIO :: (a -> IO b) -> Recorder b -> Recorder a-cmapIO f Recorder{ logger_ } =-  Recorder-    { logger_ = (liftIO . f) >=> logger_ }--renderPrettyWithSeverity :: Pretty a => WithSeverity a -> Text-renderPrettyWithSeverity =-  renderWithSeverity renderPretty--renderWithSeverity :: (a -> Text) -> WithSeverity a -> Text-renderWithSeverity f msgWithSev =-  renderSeverity (getSeverity msgWithSev) <> " " <> f (getMsg msgWithSev)--renderPretty :: Pretty a => a -> Text-renderPretty a =-  let-    docToText = renderStrict . layoutPretty defaultLayoutOptions-  in-    docToText (pretty a)--renderWithTimestamp :: Text -> IO Text-renderWithTimestamp msg = do-  t <- getCurrentTime-  let timeStamp = utcTimeToText t-  pure $ "[" <> timeStamp <> "]" <> msg-  where-    utcTimeToText utcTime = T.pack $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S%6QZ" utcTime--renderSeverity :: Severity -> Text-renderSeverity = \ case-  Debug -> "[DEBUG]"-  Info -> "[INFO]"-  Warning -> "[WARNING]"-  Error -> "[ERROR]"--toCologAction :: (MonadIO m, HasCallStack) => Recorder msg -> Colog.LogAction m msg-toCologAction (Recorder logger_) = Colog.LogAction $ \msg -> do-    logger_ msg--fromCologAction :: (HasCallStack) => Colog.LogAction IO msg -> Recorder msg-fromCologAction (Colog.LogAction logger_) = Recorder $ \msg -> do-    liftIO $ logger_ msg
haskell-debugger/GHC/Debugger/Monad.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE MultilineStrings #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}@@ -11,20 +13,25 @@  module GHC.Debugger.Monad where +import System.Environment+import System.Process+import Control.Concurrent+import Control.Concurrent.Async+import Control.Exception import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Reader import Data.Function+import Data.Functor.Contravariant import Data.IORef import Data.Maybe import Data.Version (makeVersion, showVersion) import Prelude hiding (mod)-import System.IO #ifdef MIN_VERSION_unix import System.Posix.Signals #endif-import qualified Data.IntMap as IM+import Data.Text (Text) import qualified Data.List as L import qualified Data.List.NonEmpty as NonEmpty @@ -34,6 +41,8 @@ import GHC.Driver.Config.Diagnostic import GHC.Driver.DynFlags as GHC import GHC.Driver.Env+import GHC.Driver.Monad+import GHC.Driver.Hooks import GHC.Driver.Errors import GHC.Driver.Errors.Types import GHC.Driver.Main@@ -52,20 +61,20 @@ import GHC.Unit.State import GHC.Unit.Module.ModSummary as GHC import GHC.Unit.Types-import GHC.Utils.Logger as GHC+import qualified GHC.Utils.Logger as GHC import GHC.Utils.Outputable as GHC import qualified GHC.LanguageExtensions as LangExt  import GHC.Debugger.Interface.Messages-import GHC.Debugger.Logger as Logger-import GHC.Debugger.Runtime.Term.Cache-import GHC.Debugger.Runtime.Term.Key import GHC.Debugger.Session import GHC.Debugger.Session.Builtin import GHC.Debugger.Runtime.Compile.Cache+import GHC.Debugger.Utils import qualified GHC.Debugger.Breakpoint.Map as BM import qualified GHC.Debugger.Runtime.Thread.Map as TM +import Colog.Core as Logger+ import {-# SOURCE #-} GHC.Debugger.Runtime.Instances.Discover (RuntimeInstancesCache, emptyRuntimeInstancesCache)  -- | A debugger action.@@ -82,18 +91,7 @@         -- ^ Maps a 'InternalBreakpointId' in Trie representation (map of Module to map of Int) to the         -- 'BreakpointStatus' it was activated with. -      , 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--      , rtinstancesCache :: IORef RuntimeInstancesCache+      , rtinstancesCache  :: IORef RuntimeInstancesCache       -- ^ RuntimeInstancesCache        , threadMap         :: IORef TM.ThreadMap@@ -102,9 +100,6 @@       , compCache         :: IORef CompCache       -- ^ Cache loaded and compiled expressions. -      , genUniq           :: IORef Int-      -- ^ Generates unique ints-       , hsDbgViewUnitId   :: Maybe UnitId       -- ^ The unit-id of the companion @haskell-debugger-view@ unit, used for       -- user-defined and built-in custom debug visualisations of values (e.g.@@ -124,9 +119,17 @@       --       -- If the user explicitly disabled custom views, use @Nothing@. -      , dbgLogger :: Recorder (WithSeverity DebuggerMonadLog)+      , dbgLogger :: LogAction Debugger DebuggerLog+      -- ^ See Note [Debugger, debuggee, and DAP logs]       } +instance GHC.HasLogger Debugger where+  getLogger = liftGhc GHC.getLogger++instance GHC.GhcMonad Debugger where+  getSession = liftGhc GHC.getSession+  setSession s = liftGhc $ GHC.setSession s+ -- | Enabling/Disabling a breakpoint data BreakpointStatus       -- | Breakpoint is disabled@@ -155,11 +158,14 @@ data RunDebuggerSettings = RunDebuggerSettings       { supportsANSIStyling :: Bool       , supportsANSIHyperlinks :: Bool+      , preferInternalInterpreter :: Bool+      , externalInterpreterStdinStream :: StdStream+      -- ^ How to determine the stdin of the external interpreter running the+      -- debuggee. If not using the external interpreter this field is unused.       }  -- | Run a 'Debugger' action on a session constructed from a given GHC invocation.-runDebugger :: Recorder (WithSeverity DebuggerMonadLog)-            -> Handle     -- ^ The handle to which GHC's output is logged. The debuggee output is not affected by this parameter.+runDebugger :: LogAction IO DebuggerLog             -> FilePath   -- ^ Cradle root directory             -> FilePath   -- ^ Component root directory             -> FilePath   -- ^ The libdir (given with -B as an arg)@@ -170,7 +176,10 @@             -> RunDebuggerSettings -- ^ Other debugger run settings             -> Debugger a -- ^ 'Debugger' action to run on the session constructed from this invocation             -> IO a-runDebugger l dbg_out rootDir compDir libdir units ghcInvocation' extraGhcArgs mainFp conf (Debugger action) = do+runDebugger l rootDir compDir libdir units ghcInvocation' extraGhcArgs mainFp conf (Debugger action) = do+  let ghcLog = liftLogIO l :: LogAction Ghc DebuggerLog+  let dbgLog = liftLogIO l :: LogAction Debugger DebuggerLog+  thisProg <- getExecutablePath   let ghcInvocation = filter (\case ('-':'B':_) -> False; _ -> True) ghcInvocation'   GHC.runGhc (Just libdir) $ do #ifdef MIN_VERSION_unix@@ -194,12 +203,28 @@           `GHC.gopt_set` GHC.Opt_IgnoreHpcChanges           `GHC.gopt_set` GHC.Opt_UseBytecodeRatherThanObjects           `GHC.gopt_set` GHC.Opt_InsertBreakpoints++          -- Enable the external interpreter by default! See #169+          -- See Note [Custom external interpreter]+          & enableExternalInterpreter conf.preferInternalInterpreter+          -- Ext interp is the same program as this, with "--external-interpreter"+          -- (this is ignored on GHC 9.14, see Note [Custom external interpreter])+          & setPgmI thisProg+          -- ideally, we'd set "external-interpreter" *before* the file+          -- descriptors. since there's no way to do that yet, we just have+          -- some logic in main to detect [writefd, readfd, --external-interpreter]+          & addOptI "--external-interpreter"++          -- Really important to force -dynamic if host is dynamic+          -- See Note [Dynamic Debuggee for dynamic debugger]+          & enableDynamicDebuggee+           & setBytecodeBackend           & enableByteCodeGeneration      GHC.modifyLogger $       -- Override the logger to output to the given handle-      GHC.pushLogHook $ const $ debuggerLoggerAction dbg_out+      GHC.pushLogHook $ const $ ghcLogAction l      dflags2 <- getLogger >>= \logger -> do       -- Set the extra GHC arguments for ALL units by setting them early in@@ -212,13 +237,48 @@       (dflags2, fileish_args, warns)         <- parseDynamicFlags logger dflags1 (map noLoc extraGhcArgs)       liftIO $ printOrThrowDiagnostics logger (initPrintConfig dflags2) (initDiagOpts dflags2) (GhcDriverMessage <$> warns)-      -- todo: consider fileish_args?       forM_ fileish_args $ \fish_arg -> liftIO $ do-        logMsg logger MCOutput noSrcSpan $ text "Ignoring extraGhcArg which isn't a recognized flag:" <+> text (unLoc fish_arg)+        GHC.logMsg logger MCOutput noSrcSpan $ text "Ignoring extraGhcArg which isn't a recognized flag:" <+> text (unLoc fish_arg)         printOrThrowDiagnostics logger (initPrintConfig dflags2) (initDiagOpts dflags2) (GhcDriverMessage <$> warns)       return dflags2 -    -- Set the session dynflags now to initialise the hsc_interp.+    -- Make sure to override the function which creates the external+    -- interpreter, because we need to keep track of the standard handles+    iserv_handles <- liftIO newEmptyMVar+    modifySession $ \h -> h+      { hsc_hooks = (hsc_hooks h)+          { createIservProcessHook = Just $ \cp -> do+              -- See Note [External interpreter buffering]+              (_, Just o, Just e, ph) <-+                createProcess cp+                  { std_in  = conf.externalInterpreterStdinStream+                  , std_out = CreatePipe+                  , std_err = CreatePipe+                  -- Override executable path+                  -- See Note [Custom external interpreter]+#if MIN_VERSION_ghc(9,15,0)+#else+                  , cmdspec = case cmdspec cp of+                      ShellCommand (words -> ws) -> ShellCommand $ unwords $ thisProg : drop 1 ws+                      RawCommand _fp args -> RawCommand thisProg args+#endif+                  }+              putMVar iserv_handles (o, e)+              return ph+          }+      }++    when (GHC.gopt GHC.Opt_ExternalInterpreter dflags2) $ liftIO $ void $ do+      -- The external interpreter is spawned lazily, so we block waiting for+      -- the handles to be available in a new thread.+      forkIO $ do+        withAsync (takeMVar iserv_handles) $ \async_handles -> do+          (serv_out, serv_err) <- wait async_handles+          concurrently_+            (forwardHandleToLogger serv_err (contramap LogDebuggeeErr l))+            (forwardHandleToLogger serv_out (contramap LogDebuggeeOut l))++    -- Initializes interpreter!     _ <- GHC.setSessionDynFlags dflags2      -- Initialise plugins here because the plugin author might already expect this@@ -230,6 +290,7 @@      -- Discover the user-given flags and targets     flagsAndTargets <- parseHomeUnitArguments mainFp compDir units ghcInvocation dflags2 rootDir+    buildWays       <- liftIO $ validateUnitsWays flagsAndTargets      -- Setup base HomeUnitGraph     setupHomeUnitGraph (NonEmpty.toList flagsAndTargets)@@ -247,13 +308,14 @@          -- Add the custom unit to the HUG         let base_dep_uids = [uid | UnitNode _ uid <- mg_mss mod_graph_base]-        addInMemoryHsDebuggerViewUnit base_dep_uids =<< getDynFlags+        addInMemoryHsDebuggerViewUnit base_dep_uids . setDynFlagWays buildWays =<< getDynFlags          tryLoadHsDebuggerViewModule l if_cache (const False) debuggerViewClassModName debuggerViewClassContents           >>= \case             Failed -> do               -- Failed to load base debugger-view module!-              logWith l Debug $ LogFailedToCompileDebugViewModule debuggerViewClassModName+              ghcLog <& DebuggerLog Logger.Debug+                (LogFailedToCompileDebugViewModule debuggerViewClassModName)               return []             Succeeded -> (debuggerViewClassModName:) . concat <$> do @@ -269,12 +331,14 @@                           _ -> False) . GHC.targetId)                       modName modContent >>= \case                     Failed -> do-                      logWith l Info $ LogFailedToCompileDebugViewModule modName+                      ghcLog <& DebuggerLog Logger.Info+                        (LogFailedToCompileDebugViewModule modName)                       return []                     Succeeded -> do                       return [modName]                 else do-                  logWith l Debug $ LogSkippingViewModuleNoPkg modName pkgName (map unitIdString base_dep_uids)+                  ghcLog <& DebuggerLog Logger.Debug+                    (LogSkippingViewModuleNoPkg modName pkgName (map unitIdString base_dep_uids))                   return []        Just uid ->@@ -320,8 +384,100 @@         dbgViewImps ++         map (GHC.IIModule . GHC.ms_mod) mss) -    runReaderT action =<< initialDebuggerState l (if loadedBuiltinModNames == [] then Nothing else Just hdv_uid)+    -- See Note [External interpreter buffering]+    setBufferings <- compileExprRemote """+      do { System.IO.hSetBuffering System.IO.stdout System.IO.LineBuffering+         ; System.IO.hSetBuffering System.IO.stderr System.IO.LineBuffering }+      """+    hscInterp <$> GHC.getSession >>= \interp ->+      liftIO $ evalIO interp setBufferings +    runReaderT action+      =<< initialDebuggerState dbgLog+          (if loadedBuiltinModNames == []+            then Nothing+            else Just hdv_uid)++{-+Note [Custom external interpreter]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+We compile a custom external interpreter server with custom commands which make+certain debugger operations possible in the remote process directly.+This allows us to avoid excessive `Term` parsing and remote execution.+(Note: we don't have custom commands just yet, but that is the vision)++The custom external interpreter is the same executable as the debugger but invoked as:++  hdb <write-fd> <read-fd> --external-interpreter++(Note: external-interpreter is not the first argument because all `-opti`s are+always inserted after the write-fd and read-fd.)++When setting up the debugger session, we essentially set by default:+  - Enable -fexternal-interpreter+  - Set -pgmi=hdb and -opti=--external-interpreter+This can be switched off by toggling `--internal-interpreter`++With GHC 9.14, we have to override the `createProcess` executable call because+of ghc's c94aaacd4c4 (GHC looks for a `-dyn` suffixed version of the custom+external `-pgmi`, in this case `hdb` (but we do not have an `hdb-dyn`).+In GHC 9.16 it is sufficient to specify the -pgmi.++We can't use the on-the-fly external interpreter from GHC 9.14 because it is+not compiled with -threaded (with 9.16 in principle could, but we really want+the custom commands)++Note: The custom external interpreter must be compiled with -fkeep-cafs!+Why that is necessary is described in the GHC source code.++Note [Dynamic Debuggee for dynamic debugger]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+A really really important point is that the debuggee MUST be linked dynamically+if the debugger was compiled dynamically (checked with `hostIsDynamic`).++Not doing this resulted in days upon days of suffering caused by a SIGILL fault.++The bug surfaces when we load a non-PIC static object of the debuggee into a+debugger that was linked dynamically with PIC. The runtime object linker does+not handle this correctly and something goes very wrong with the relocated+debuggee code.++Notably, this bug didn't surface on macOS because the static objects are also+compiled with -fPIC, and it didn't show up when using the distributed iserv+executables because that has very few dyn-link-time dependencies and for some+reason that doesn't trigger the bug. Adding more unused package dependencies+was sufficient to re-trigger it on windows.++Therefore, we always use -dynamic for compiling and loading the debuggee if the+debugger is dynamic (`hostIsDynamic`).++On Windows, the debugger will be static and we'll resort to statically linking+the debuggee too. There won't be a PIC mismatch so this should work fine.++Note [External interpreter buffering]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+When we launch the external interpreter process, we create pipes for+stdin/stdout/stderr instead of inheriting the current process' handles.+This allows us to cleanly separate the debuggee output from the debugger+output, without needing to redirect handles or the like.++However, using a pipe instead of a handle connected to a TTY means that, by+default, the line buffering will be block based rather than line buffered.++> Newly opened streams are normally fully buffered, with one exception: a+  stream connected to an interactive device such as a terminal is initially+  line buffered.^[1]++We depend on line buffering to forward output from these handles to the+debugger output (see `forwardHandleToLogger`).++Therefore, after loading the modules, we evaluate on the remote process:++  hSetBuffering stdout LineBuffering+  hSetBuffering stderr LineBuffering++[1] https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.5/html_node/Buffering-Concepts.html+-} --------------------------------------------------------------------------------  -- | Run downsweep on the currently set targets (see @hsc_targets@)@@ -357,7 +513,7 @@ -- | Returns @Just modName@ if the given module was successfully loaded tryLoadHsDebuggerViewModule   :: GhcMonad m-  => Recorder (WithSeverity DebuggerMonadLog)+  => LogAction IO DebuggerLog   -> Maybe ModIfaceCache   -> (GHC.Target -> Bool)   -- ^ Predicate to determine which of the existing@@ -375,9 +531,9 @@   -- modules we're trying to load and compile.   restore_logger <- GHC.getLogger   GHC.modifyLogger $-    -- Emit it all as Debug-level logs+    -- Emit it all as Debug-level debugger logs     GHC.pushLogHook $ const $ \_ _ _ sdoc ->-      logWith l Logger.Debug $ LogSDoc dflags sdoc+      l <& DebuggerLog Logger.Debug (LogSDoc dflags sdoc)    -- Make the target   dvcT <- liftIO $ makeInMemoryHsDebuggerViewTarget modName modContents@@ -394,7 +550,7 @@    -- Restore logger   GHC.modifyLogger $-    GHC.pushLogHook (const $ putLogMsg restore_logger)+    GHC.pushLogHook (const $ GHC.putLogMsg restore_logger)     return result@@ -426,6 +582,7 @@         | UnitNode _deps uid <- mg_mss mod_graph         , "haskell-debugger-view" `L.isPrefixOf` unitIdString uid             || "hskll-dbggr-vw" `L.isPrefixOf` unitIdString uid+            || "haskell-debug_" `L.isPrefixOf` unitIdString uid         ]        -- If the haskell-debugger-view is in the dependency graph, it must have@@ -451,68 +608,16 @@       error "Multiple unit-ids found for haskell-debugger-view in the transitive closure?!"  ----------------------------------------------------------------------------------- Variable references------------------------------------------------------------------------------------- | Find a variable's associated Term and Name by reference ('Int')-lookupVarByReference :: Int -> Debugger (Maybe TermKey)-lookupVarByReference i = do-  ioref <- asks varReferences-  (rm, _) <- readIORef ioref & liftIO-  return $ IM.lookup i rm---- | Finds or creates an integer var reference for the given 'TermKey'.--- TODO: Arguably, this mapping should be part of the debug-adapter, and--- haskell-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-  liftIO $ writeIORef ioref mempty---------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- --- | Generate a new unique 'Int'-freshInt :: Debugger Int-freshInt = do-  ioref <- asks genUniq-  i <- readIORef ioref & liftIO-  let !i' = i+1-  writeIORef ioref i'  & liftIO-  return i- -- | Initialize a 'DebuggerState'-initialDebuggerState :: Recorder (WithSeverity DebuggerMonadLog) -> Maybe UnitId -> GHC.Ghc DebuggerState+initialDebuggerState :: LogAction Debugger DebuggerLog -> Maybe UnitId -> GHC.Ghc DebuggerState initialDebuggerState l hsDbgViewUid =   DebuggerState <$> liftIO (newIORef BM.empty)-                <*> liftIO (newIORef mempty)                 <*> liftIO (newIORef emptyRuntimeInstancesCache)                 <*> liftIO (newIORef TM.emptyThreadMap)                 <*> liftIO (newIORef emptyCompCache)-                <*> liftIO (newIORef 0)                 <*> pure hsDbgViewUid                 <*> pure l @@ -520,8 +625,6 @@ liftGhc :: GHC.Ghc a -> Debugger a liftGhc = Debugger . ReaderT . const ---------------------------------------------------------------------------------- data DebuggerFailedToLoad = DebuggerFailedToLoad instance Exception DebuggerFailedToLoad instance Show DebuggerFailedToLoad where@@ -537,6 +640,14 @@     "Cannot use unsupported haskell-debugger-view version found in the transitive closure: " ++ showVersion actual ++     " (supported: " ++ L.intercalate ", " (map showVersion supported) ++ ")" +expectRight :: Exception e => Either e a -> Debugger a+expectRight s = case s of+  Left e -> do+    logSDoc Logger.Error (text $ displayException e)+    liftIO $ throwIO e+  Right a -> do+    pure a+ -------------------------------------------------------------------------------- -- * Modules --------------------------------------------------------------------------------@@ -591,51 +702,40 @@   _              -> do seqTerm hsc_env t  ----------------------------------------------------------------------------------- * Instances-----------------------------------------------------------------------------------instance GHC.HasLogger Debugger where-  getLogger = liftGhc GHC.getLogger--instance GHC.GhcMonad Debugger where-  getSession = liftGhc GHC.getSession-  setSession s = liftGhc $ GHC.setSession s---------------------------------------------------------------------------------- -- * Logging -------------------------------------------------------------------------------- --- | The logger action used to log GHC output-debuggerLoggerAction :: Handle -> GHC.LogAction-debuggerLoggerAction h a b c d = do-  hSetEncoding h utf8 -- GHC output uses utf8-  -- potentially use the `Recorder` here?-  defaultLogActionWithHandles h h a b c d+-- | A debugger log. May include debuggee ouput.+data DebuggerLog+  = DebuggerLog !Logger.Severity !DebuggerMessage+  | GHCLog !GHC.LogFlags !MessageClass !SrcSpan !SDoc+  | LogDebuggeeOut !Text+  | LogDebuggeeErr !Text -data DebuggerMonadLog-  = LogFailedToCompileDebugViewModule GHC.ModuleName-  | LogSkippingViewModuleNoPkg GHC.ModuleName String [String]-  | LogSDoc DynFlags SDoc+-- | A debugger log message+data DebuggerMessage+  = LogSDoc !DynFlags !SDoc+  | LogFailedToCompileDebugViewModule !GHC.ModuleName+  | LogSkippingViewModuleNoPkg !GHC.ModuleName String [String] -instance Pretty DebuggerMonadLog where-  pretty = \ case+instance Show DebuggerMessage where+  show = \ case     LogFailedToCompileDebugViewModule mn ->-      pretty $ "Failed to compile built-in " ++ moduleNameString mn ++ " module! Ignoring these custom debug views."+      "Failed to compile built-in " ++ moduleNameString mn ++ " module! Ignoring these custom debug views."     LogSkippingViewModuleNoPkg mn pkg uids ->-      pretty $ "Skipping compilation of built-in " ++ moduleNameString mn ++ " module because package "-                ++ show pkg ++ " wasn't found in dependencies " ++ show uids-    LogSDoc dflags doc ->-      pretty $ showSDoc dflags doc+      "Skipping compilation of built-in " ++ moduleNameString mn ++ " module because package "+          ++ show pkg ++ " wasn't found in dependencies " ++ show uids+    LogSDoc dflags doc -> showSDoc dflags doc  logSDoc :: Logger.Severity -> SDoc -> Debugger () logSDoc sev doc = do   dflags <- getDynFlags   l <- asks dbgLogger-  logWith l sev (LogSDoc dflags doc)+  l <& DebuggerLog sev (LogSDoc dflags doc) -logAction :: Recorder (WithSeverity DebuggerMonadLog) -> DynFlags -> GHC.LogAction-logAction l dflags = \_ msg_class _ sdoc -> do-    logWith l (msgClassSeverity msg_class) $ LogSDoc dflags sdoc+ghcLogAction :: LogAction IO DebuggerLog -> GHC.LogAction+ghcLogAction l = \logflags mclass srcSpan sdoc -> do+    liftLogIO l <& GHCLog logflags mclass srcSpan sdoc  msgClassSeverity :: MessageClass -> Logger.Severity msgClassSeverity = \case@@ -644,10 +744,6 @@   MCInteractive -> Info   MCDump -> Debug   MCInfo -> Info-  MCDiagnostic sev _ _ -> ghcSevSeverity sev--ghcSevSeverity :: GHC.Severity -> Logger.Severity-ghcSevSeverity = \case-  SevIgnore -> Debug -- ?-  SevWarning -> Logger.Warning-  SevError -> Logger.Error+  MCDiagnostic SevIgnore _ _ -> Debug -- ?+  MCDiagnostic SevWarning _ _ -> Logger.Warning+  MCDiagnostic SevError _ _ -> Logger.Error
haskell-debugger/GHC/Debugger/Run.hs view
@@ -20,7 +20,6 @@ import Data.Maybe import System.FilePath import System.Directory-import qualified Prettyprinter as Pretty  import GHC import GHC.Builtin.Names (gHC_INTERNAL_GHCI_HELPERS)@@ -33,7 +32,6 @@ import qualified GHC.Unit.Module.ModSummary as GHC 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 qualified GHC.Data.Strict as Strict @@ -41,24 +39,17 @@ import GHC.Debugger.Monad import GHC.Debugger.Utils import GHC.Debugger.Interface.Messages-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger import qualified GHC.Debugger.Breakpoint.Map as BM import GHC.Debugger.Runtime.Thread -data EvalLog-  = LogEvalModule GHC.Module--instance Pretty EvalLog where-  pretty = \ case-    LogEvalModule modl -> "Eval Module Context:" Pretty.<+> pretty (GHC.showSDocUnsafe (ppr modl))- -------------------------------------------------------------------------------- -- * Evaluation --------------------------------------------------------------------------------  -- | Run a program with debugging enabled-debugExecution :: Recorder (WithSeverity EvalLog) -> FilePath -> EntryPoint -> [String] {-^ Args -} -> Debugger EvalResult-debugExecution recorder entryFile entry args = do+debugExecution :: FilePath -> EntryPoint -> [String] {-^ Args -} -> Debugger EvalResult+debugExecution entryFile 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?   modSummaryOfEntryFile <- findUnitIdOfEntryFile entryFile@@ -69,7 +60,7 @@     evalModule = mkModule (RealUnit (Definite unitIdOfEntryFile))                                          (moduleName modOfEntryFile) -  logWith recorder Info $ LogEvalModule evalModule+  logSDoc Logger.Debug $ "Eval Module Context:" <+> ppr evalModule   old_context <- GHC.getContext   GHC.setContext [GHC.IIModule evalModule] @@ -130,20 +121,17 @@ -- | 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  doStepOut :: Debugger EvalResult doStepOut = do-  leaveSuspendedState   mb_span <- getCurrentBreakSpan   case mb_span of     Nothing ->@@ -164,7 +152,6 @@ -- this enclosing span. doLocalStep :: Debugger EvalResult doLocalStep = do-  leaveSuspendedState   mb_span <- getCurrentBreakSpan   case mb_span of     Nothing -> error "not stopped at a breakpoint?!"
haskell-debugger/GHC/Debugger/Runtime.hs view
@@ -2,11 +2,9 @@ module GHC.Debugger.Runtime where  import Control.Monad.Reader-import qualified Data.List as L  import GHC import GHC.Utils.Outputable-import GHC.Types.FieldLabel import GHC.Runtime.Eval import GHC.Runtime.Heap.Inspect @@ -27,12 +25,9 @@      FromPath k pf -> do        term <- obtainTerm k        liftIO $ expandTerm hsc_env $ case term of-         Term{dc=Right dc, subTerms} -> case pf of+         Term{dc=Right _, subTerms} -> case pf of            PositionalIndex ix -> subTerms !! (ix-1)-           LabeledField fl    ->-             case L.findIndex (== fl) (map flSelector $ dataConFieldLabels dc) of-               Just ix -> subTerms !! ix-               Nothing -> error "Couldn't find labeled field in dataConFieldLabels"+           LabeledField n _ -> subTerms !! (n-1)          NewtypeWrap{wrapped_term} ->            wrapped_term -- regardless of PathFragment          RefWrap{wrapped_term} ->@@ -58,4 +53,3 @@     return term{wrapped_term=wt'}   Suspension{val, ty} -> cvObtainTerm hsc_env defaultDepth False ty val   Prim{} -> return term-
haskell-debugger/GHC/Debugger/Runtime/Compile.hs view
@@ -20,9 +20,7 @@ import GHC  import GHC.Debugger.Monad-import GHC.Debugger.Utils import GHC.Debugger.Runtime.Compile.Cache-import GHC.Debugger.Logger as Logger  -- | Compile and load a fully qualified external variable, potentially applied -- to a few type arguments (i.e. by specializing it). E.g. @"GHC.Base" "pure" ["IO"]@@ -57,7 +55,7 @@    case lookupCompRaw raw_expr compCacheVal of     Just fhv -> do-      logSDoc Logger.Debug (text "Cache hit! on" <+> text raw_expr)+      -- logSDoc Logger.Debug (text "Cache hit! on" <+> text raw_expr)       return fhv     Nothing  -> do       fhv <- compileExprRemote raw_expr
haskell-debugger/GHC/Debugger/Runtime/Eval.hs view
@@ -2,38 +2,45 @@  -- | Higher-level interface to evaluating things in the (possibly remote) debuggee process module GHC.Debugger.Runtime.Eval-  ( evalExpr+  (+    -- * Raw evaluation+    evalExpr, evalString++    -- ** Error handling   , handleSingStatus   , BadEvalStatus(..)++    -- ** Re-exports+  , EvalExpr(..)   ) where  import GHC-import GHC.Runtime.Interpreter as Interp import GHCi.RemoteTypes import GHCi.Message import Control.Exception import GHC.Driver.Env import GHC.Driver.Config import Control.Monad.IO.Class+import qualified GHC.Runtime.Interpreter as Interp -import GHC.Debugger.Utils import GHC.Debugger.Monad-import GHC.Debugger.Logger as Logger  -- * Evaluating things on debuggee ---------------------------------------------  -- | Evaluate a raw 'EvalExpr' which represents a debuggee expression of type @IO [a]@ evalExpr :: EvalExpr ForeignHValue -> Debugger (Either BadEvalStatus [ForeignHValue]) evalExpr eval_expr = do-  logSDoc Logger.Debug (text "evalExpr:" <+> (text (show (mkUnit eval_expr))))   hsc_env <- getSession   let eval_opts = initEvalOpts (hsc_dflags hsc_env) EvalStepNone   handleMultiStatus <$>-    liftIO (evalStmt (hscInterp hsc_env) eval_opts eval_expr)-  where-    mkUnit :: EvalExpr a -> EvalExpr ()-    mkUnit EvalThis{} = EvalThis ()-    mkUnit (EvalApp a b) = mkUnit a `EvalApp` mkUnit b+    liftIO (Interp.evalStmt (hscInterp hsc_env) eval_opts eval_expr)++-- | Evaluate a foreign value of type @IO String@ to a @String@+evalString :: ForeignRef (IO String) -> Debugger String+evalString string_fhv = do+  hsc_env <- getSession+  liftIO $+    Interp.evalString (hscInterp hsc_env) (castForeignRef string_fhv)  -- ** Handling evaluation results ---------------------------------------------- 
haskell-debugger/GHC/Debugger/Runtime/Eval/RemoteExpr.hs view
@@ -29,16 +29,12 @@ import Control.Monad.Except import GHC.Exts import GHC-import GHC.Driver.Env-import GHC.Runtime.Interpreter as Interp hiding (evalIO, evalString)-import qualified GHC.Runtime.Interpreter as Interp import Control.Monad.Reader import GHCi.RemoteTypes -import GHC.Debugger.Logger as Logger import GHC.Debugger.Monad-import GHC.Debugger.Utils-import GHC.Debugger.Runtime.Eval as Raw+import GHC.Debugger.Runtime.Eval (handleSingStatus, BadEvalStatus(..), EvalExpr(..))+import qualified GHC.Debugger.Runtime.Eval as Raw import GHC.Debugger.Runtime.Compile  --------------------------------------------------------------------------------@@ -181,8 +177,6 @@ -} evalIOList :: RemoteExpr (IO [a]) -> Debugger (Either BadEvalStatus [ForeignRef a]) evalIOList expr = runExceptT $ do-  lift $ logSDoc Logger.Debug (text "evalIOList" <+> text (show expr))-   res_fv <- debuggeeEval expr    r <- lift $ Raw.evalExpr (EvalThis (castForeignRef res_fv))@@ -191,10 +185,8 @@ -- | Execute an @IO String@ on the remote process and serialize the string back to the debugger. evalIOString :: RemoteExpr (IO String) -> Debugger (Either BadEvalStatus String) evalIOString expr = runExceptT $ do-  lift $ logSDoc Logger.Debug (text "evalIOString" <+> text (show expr))   res_fv <- debuggeeEval expr-  hsc_env <- lift getSession-  liftIO (Interp.evalString (hscInterp hsc_env) (castForeignRef res_fv))+  lift $ Raw.evalString res_fv  -------------------------------------------------------------------------------- -- ** Recursive evaluation of 'RemoteExpr' (lower-level)
haskell-debugger/GHC/Debugger/Runtime/Eval/RemoteExpr/Builtin.hs view
@@ -19,12 +19,14 @@ import Data.Word import Foreign.C.String import GHC.Conc.Sync+import GHC.InfoProv import GHC.Unit.Module import GHC.Stack.CloneStack import GHC.Exts.Heap import GHC.Exts.Heap.Closures import GHC.Debugger.Runtime.Eval.RemoteExpr (RemoteExpr) import qualified GHC.Debugger.Runtime.Eval.RemoteExpr as Remote+import GHC.Stack.Annotation.Experimental  -- | Remote 'GHC.Stack.CloneStack.cloneThreadStack' cloneThreadStack :: RemoteExpr ThreadId -> RemoteExpr (IO StackSnapshot)@@ -42,6 +44,10 @@ ssc_stack :: RemoteExpr StgStackClosure -> RemoteExpr [StackFrame] ssc_stack = Remote.app $ Remote.var (mkModuleName "GHC.Exts.Heap.Closures") "ssc_stack" [] +-- | Remote 'GHC.Internal.Stack.Decode.decodeStackWithIpe"+decodeStackWithIpe :: RemoteExpr StackSnapshot -> RemoteExpr (IO [(StackFrame, Maybe InfoProv)])+decodeStackWithIpe = Remote.app $ Remote.var (mkModuleName "GHC.Internal.Stack.Decode") "decodeStackWithIpe" []+ -- | Remote 'GHC.Exts.Heap.getClosureData' getClosureData :: RemoteExpr StgStackClosure -> RemoteExpr (IO Closure) getClosureData = Remote.app $ Remote.var (mkModuleName "GHC.Exts.Heap") "getClosureData" ["GHC.Exts.LiftedRep", "_"]@@ -80,3 +86,7 @@ f `compose` g = Remote.app (Remote.app composeVar f) g where   composeVar :: RemoteExpr ((b -> c) -> (a -> b) -> (a -> c))   composeVar = Remote.var (mkModuleName "GHC.Base") "." []++displayStackAnnotation :: RemoteExpr SomeStackAnnotation -> RemoteExpr String+displayStackAnnotation = Remote.app $+  Remote.var (mkModuleName "GHC.Stack.Annotation.Experimental") "displayStackAnnotation" ["SomeStackAnnotation"]
haskell-debugger/GHC/Debugger/Runtime/Instances.hs view
@@ -10,7 +10,7 @@ import Control.Monad.Reader  import GHC.Debugger.Monad-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger import GHC.Debugger.Runtime.Instances.Discover import GHC.Debugger.Runtime.Term.Parser 
haskell-debugger/GHC/Debugger/Runtime/Instances/Discover.hs view
@@ -42,7 +42,7 @@  import GHC.Debugger.Monad import GHC.Debugger.Session.Builtin-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger  -------------------------------------------------------------------------------- -- * The Cache-level interface for runtime 'DebugView' instances
− haskell-debugger/GHC/Debugger/Runtime/Term/Cache.hs
@@ -1,35 +0,0 @@-{-# LANGUAGE GADTs, DataKinds #-}-module GHC.Debugger.Runtime.Term.Cache where--import GHC.Types.Var.Env--import GHC.Debugger.Runtime.Term.Key--import Data.Map (Map)-import qualified Data.Map as M------------------------------------------------------------------------------------- * TermKeyMap------------------------------------------------------------------------------------- | Mapping from 'TermKey' to @a@. Backs 'TermCache', but is more general.-type TermKeyMap a = IdEnv (Map [PathFragment True] 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
haskell-debugger/GHC/Debugger/Runtime/Term/Key.hs view
@@ -27,10 +27,9 @@   -- | A positional index is an index from 1 to inf   PositionalIndex :: Int -> PathFragment b   -- | A labeled field indexes a datacon fields by name-  LabeledField    :: Name -> PathFragment b-  -- | Similar to LabeledField, but originates from a custom 'DebugView'-  -- instance rather than a proper data con label (hence why we don't have a name).-  CustomField     :: String -> PathFragment True+  -- The position is given by the 'Int'+  -- The name is cosmetic.+  LabeledField    :: Int -> Name -> PathFragment b deriving instance Eq (PathFragment b) deriving instance Ord (PathFragment b) @@ -41,14 +40,5 @@  instance Outputable (PathFragment b) where   ppr (PositionalIndex i) = text "_" <> ppr i-  ppr (LabeledField n)    = ppr n-  ppr (CustomField s)     = text s+  ppr (LabeledField _ n)    = ppr n --- | >>> unconsTermKey (FromPath (FromPath (FromId hi) (Pos 1)) (Pos 2))--- (hi, [1, 2])-unconsTermKey :: TermKey -> (Id, [PathFragment True])-unconsTermKey = go [] where-  go acc (FromId i)                       = (i, reverse acc)-  go acc (FromPath k (PositionalIndex i)) = go (PositionalIndex i:acc) k-  go acc (FromPath k (LabeledField n))    = go (LabeledField n:acc) k-  go acc (FromCustomTerm k s _)           = go (CustomField s:acc) k
haskell-debugger/GHC/Debugger/Runtime/Term/Parser.hs view
@@ -16,14 +16,13 @@ import GHC.Runtime.Interpreter as Interp import GHC.Types.Name (nameOccName) import GHC.Types.Name.Occurrence (occNameString)-import qualified GHC.Debugger.Logger as Logger+import qualified Colog.Core as Logger import GHC.Utils.Outputable (text, (<+>), ppr) import Control.Monad.Reader import GHC.Core.TyCo.Compare import GHC.Stack  import GHC.Debugger.Monad-import GHC.Debugger.Utils (expectRight)  import qualified GHC.Debugger.Runtime.Eval.RemoteExpr as Remote import qualified GHC.Debugger.Runtime.Compile as Comp@@ -162,7 +161,7 @@   case t of     Term{subTerms}       | idx < length subTerms -> do-          liftDebugger $ logSDoc Logger.Debug (ppr subTerms)+          -- liftDebugger $ logSDoc Logger.Debug (ppr subTerms)           focus (pure (subTerms !! idx)) refreshTerm       | otherwise -> parseError (TermParseError $ "missing subterm index " <> show idx)     other -> parseError (TermParseError $ "expected Term with subterms, got " <> termTag other)@@ -224,11 +223,11 @@ isSuspension :: TermParser Bool isSuspension = focus refreshTerm $ do   t <- anyTerm-  traceTerm+  -- traceTerm   case t of     Suspension{} -> pure True-    other -> do-      liftDebugger $ logSDoc Logger.Debug (text $ termTag other)+    _other -> do+      -- liftDebugger $ logSDoc Logger.Debug (text $ termTag other)       return False  -- | Obtain a Term from a ForeignHValue
haskell-debugger/GHC/Debugger/Runtime/Thread.hs view
@@ -30,8 +30,7 @@ import GHCi.Message import GHCi.RemoteTypes -import GHC.Debugger.Utils-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger import GHC.Debugger.Monad import GHC.Debugger.Interface.Messages import GHC.Debugger.Runtime.Term.Parser
haskell-debugger/GHC/Debugger/Runtime/Thread/Stack.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OrPatterns #-} {-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultilineStrings #-} {-# LANGUAGE QualifiedDo #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE LambdaCase #-}@@ -7,23 +8,24 @@  -- | Decoding the stack of a thread at runtime module GHC.Debugger.Runtime.Thread.Stack-  ( getRemoteThreadStackCopy-  , getRemoteThreadIPEStack+  ( StackFrameInfo(..)+  , getRemoteThreadStackCopy   ) where  import Data.Bits import Data.Maybe-import Control.Applicative import Control.Concurrent+import Control.Applicative import Control.Monad import Control.Monad.IO.Class-import GHC.Stack.CloneStack import GHC.Exts.Heap.ClosureTypes import GHC.Utils.Encoding.UTF8+import GHC.InfoProv  import GHC import GHC.Builtin.Types import GHC.Runtime.Heap.Inspect+import qualified GHC.Stack.Types as Stack  import GHC.Driver.Env import GHC.Runtime.Interpreter as Interp@@ -32,22 +34,34 @@ import GHCi.Message import GHCi.RemoteTypes -import GHC.Debugger.Utils-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger import GHC.Debugger.Monad import GHC.Debugger.Runtime.Term.Parser import GHC.Debugger.Runtime.Eval import qualified GHC.Debugger.Runtime.Eval.RemoteExpr as Remote import qualified GHC.Debugger.Runtime.Eval.RemoteExpr.Builtin as Remote +--------------------------------------------------------------------------------+-- * Thread stack frames+--------------------------------------------------------------------------------++-- | Information about a stack frame+data StackFrameInfo+  -- | Information derived from an IPE entry+  = StackFrameIPEInfo !InfoProv+  -- | User-defined Stack Frame annotation+  | StackFrameAnnotation !(Maybe Stack.SrcLoc) !String+  -- | Information derived from a continuation BCO breakpoint info.+  | StackFrameBreakpointInfo !InternalBreakpointId+ -- | Clone the stack of the given remote thread and get the breakpoint ids of available frames-getRemoteThreadStackCopy :: ForeignRef ThreadId -> Debugger [InternalBreakpointId]+getRemoteThreadStackCopy :: ForeignRef ThreadId -> Debugger [StackFrameInfo] getRemoteThreadStackCopy threadIdRef = do    l <- Remote.evalIOList $ Remote.do     clonedStack <- Remote.cloneThreadStack (Remote.ref threadIdRef)-    frames      <- Remote.decodeStack      clonedStack-    Remote.return (Remote.ssc_stack frames)+    frames      <- Remote.decodeStackWithIpe clonedStack+    Remote.return frames    case l of     Left (EvalRaisedException e) -> do@@ -56,73 +70,87 @@     Left e -> do       logSDoc Logger.Warning (text "Failed to decode the stack with" <+> text (show e) $$ text "No StackTrace will be returned...")       return []-    Right stack_frames_fvs -> fmap (catMaybes . catMaybes) $-      forM stack_frames_fvs $ \stack_frame_fv ->+    Right stack_frames_fvs -> fmap catMaybes $+      forM stack_frames_fvs $ \ stack_frame_fv -> do         obtainParsedTerm "ghc-heap:StackFrame" 2 True anyTy{-todo:stackframety?-} (castForeignRef stack_frame_fv)-          ((Just <$> retBCOParser) <|> pure Nothing) >>= \case+          stackFrameInfoParser >>= \case             Left errs -> do               logSDoc Logger.Error (vcat (map (text . getTermErrorMessage) errs))               return Nothing             Right tm ->               return tm --- | Try to get an IPE stacktrace.------ At the moment, we assume IPE stacktraces are always empty @[]@ for threads--- with interpreter frames.------ Really, as long as there is IPE information, this function should return--- StackEntries for all frames, including the interpreter ones, since these are--- typically be interleaved with "normal" frames.----getRemoteThreadIPEStack :: ForeignRef ThreadId -> Debugger [StackEntry]-getRemoteThreadIPEStack threadIdRef = do-  l <- Remote.evalIOList $ Remote.do-    clonedStack <- Remote.cloneThreadStack (Remote.ref threadIdRef)-    Remote.decode clonedStack-  case l of-    Left (EvalRaisedException e) -> do-      logSDoc Logger.Info (text "Failed to decode the stack with" <+> text (show e) $$ text "This is likely bug #26640 in the decoder, which has been fixed for 9.14.2 and forward. No StackTrace could be produced...")-      return []-    Left e -> do-      logSDoc Logger.Warning (text "Failed to decode the stack with" <+> text (show e) $$ text "No StackTrace will be returned...")-      return []-    Right entries_fvs -> do-      mapM (\entry_fv -> do-          stack_entry <- obtainParsedTerm "StackEntry" 2 True anyTy{-stackentry, but we won't look at the ty...-} entry_fv stackEntryParser-          case stack_entry of-            Left errs -> do-              logSDoc Logger.Error (vcat (map (text . getTermErrorMessage) errs))-              liftIO $ fail "Failed to parse @StackEntry@ from decoding thread stack!"-            Right se ->-              pure se-        ) (map castForeignRef entries_fvs)- -------------------------------------------------------------------------------- -- ** Decoding Stack Frames ---------------------------------------------------- -------------------------------------------------------------------------------- -stackEntryParser :: TermParser StackEntry-stackEntryParser = do-    StackEntry <$> subtermWith 0 stringParser <*> subtermWith 1 stringParser <*> subtermWith 2 stringParser <*> pure INVALID_OBJECT{-this is a stub-}+-- | Try to decode a 'StackFrameInfo' from a @(StackFrame, Maybe InfoProv)@ term+stackFrameInfoParser :: TermParser (Maybe StackFrameInfo)+stackFrameInfoParser = do+  -- Try a stack annotation first+  stackAnno <- subtermWith 0 stackAnnoParser+  case stackAnno of+    Nothing -> do+      -- Try IPE next+      mipe <- subtermWith 1 (maybeParser infoProvParser)+      case mipe of+        Nothing -> do+          -- Try decoding a continuation BCO with a breakpoint next+          fmap StackFrameBreakpointInfo+            <$> subtermWith 0 retBCOParser+        Just ipe -> pure $+          Just (StackFrameIPEInfo ipe)+    Just (srcLoc, ann) -> pure $+      Just (StackFrameAnnotation srcLoc ann) +-- | Decode an 'InfoProv' from an @InfoProv@ term+infoProvParser :: TermParser InfoProv+infoProvParser = InfoProv+  <$> subtermWith 0 stringParser -- ipName+  <*> pure INVALID_OBJECT -- ipDesc (this is a stub)+  <*> subtermWith 2 stringParser -- ipTyDesc+  <*> subtermWith 3 stringParser -- ipLabel+  <*> subtermWith 4 stringParser -- ipUnitId+  <*> subtermWith 5 stringParser -- ipMod+  <*> subtermWith 6 stringParser -- ipSrcFile+  <*> subtermWith 7 stringParser -- ipSrcSpan++-- | Try to decode an 'InternalBreakpointId' from a @StackFrame@ term retBCOParser :: TermParser (Maybe InternalBreakpointId) retBCOParser = do   -- Match against "RetBCO" frames and extract the BCOClosure information-  Suspension{val, ctype=BCO}-    {-"the otherwise case: Unknown closure", hence Suspension-}-      <- matchConstructorTerm "RetBCO" *> subtermWith 1 (subtermWith 0{-take from Box-} anyTerm)-  liftDebugger $ do+  (matchConstructorTerm "RetBCO" *> subtermWith 1 (subtermWith 0{-take from Box-} (Just <$> anyTerm)) <|> pure Nothing)+    >>= \case+      Just Suspension{val, ctype=BCO} -> do+        {-"the otherwise case: Unknown closure", hence Suspension-} -    -- Decode the BCO closure using 'getClosureData' on the foreign heap-    bco_closure_fv <- expectRight =<< Remote.evalIO-      (Remote.getClosureData (Remote.ref (castForeignRef val)))+        -- Decode the BCO closure using 'getClosureData' on the foreign heap+        bco_closure_fv <- liftDebugger $+          expectRight =<< Remote.evalIO+            (Remote.getClosureData (Remote.ref (castForeignRef val))) -    r <- obtainParsedTerm "BCO BRK_FUN info" 2 True anyTy (castForeignRef bco_closure_fv) bcoInternalBreakpointId-    case r of-      Left err -> liftIO $ fail (show err)-      Right t  -> return t+        r <- liftDebugger $+          obtainParsedTerm "BCO BRK_FUN info" 2 True anyTy (castForeignRef bco_closure_fv) bcoInternalBreakpointId+        case r of+          Left err -> fail (show err)+          Right t  -> return t+      _ -> pure Nothing +-- | Try to decode an 'StackAnnotation' from a @StackFrame@ term+stackAnnoParser :: TermParser (Maybe (Maybe Stack.SrcLoc, String))+stackAnnoParser = do+  -- Match against "AnnFrame" frames and extract the 'SomeStackAnnotation'+  (matchConstructorTerm "AnnFrame" *> subtermWith 1 (subtermWith 0{-take from Box-} (Just <$> anyTerm)) <|> pure Nothing)+    >>= \case+      Just Term{val} -> do+        stack_anno <- liftDebugger $+          expectRight =<< Remote.evalString+            (Remote.displayStackAnnotation (Remote.ref (castForeignRef val)))++        pure $ Just (Nothing {- No source locations yet :( -}, stack_anno)+      _ ->+        pure Nothing+ -- | Parse an 'InternalBreakpointId' out of a 'BCOClosure' term. bcoInternalBreakpointId :: TermParser (Maybe InternalBreakpointId) bcoInternalBreakpointId = do@@ -181,10 +209,10 @@           | otherwise = 0 :: Int      let find_ixs_fv = Remote.raw $-          "\\x -> let index_at n = GHC.Word.W16# (GHC.Base.indexWord16Array# x (n GHC.Exts.+# " ++ show bRK_FUN_offset ++ "#)) " ++-                   "in if (index_at 0# Data.Bits..&. 0xFF) == 66{-bci_BRK_FUN-} then \-                        Just (index_at 1#, index_at 2#, index_at 3#, index_at 4#, index_at 5#) \-                      else Nothing"+          "\\x -> let index_at n = GHC.Word.W16# (GHC.Base.indexWord16Array# x (n GHC.Exts.+# " ++ show bRK_FUN_offset ++ """#))+                    in if (index_at 0# Data.Bits..&. 0xFF) == 66{-bci_BRK_FUN-} then+                        Just (index_at 1#, index_at 2#, index_at 3#, index_at 4#, index_at 5#)+                      else Nothing"""     rs_fv <- expectRight =<< Remote.eval       (find_ixs_fv `Remote.app` Remote.untypedRef instrs_array_fv) 
haskell-debugger/GHC/Debugger/Session.hs view
@@ -7,6 +7,7 @@ module GHC.Debugger.Session (   parseHomeUnitArguments,   setupHomeUnitGraph,+  validateUnitsWays,   TargetDetails(..),   Target(..),   toGhcTarget,@@ -21,6 +22,10 @@   setCacheDirs,   setBytecodeBackend,   enableByteCodeGeneration,+  enableExternalInterpreter,+  enableDynamicDebuggee,+  setPgmI, addOptI,+  setDynFlagWays   )   where @@ -41,6 +46,7 @@ import qualified System.Environment as Env  import qualified GHC+import GHC.Platform.Ways import GHC.Driver.DynFlags as GHC import GHC.Driver.Monad import qualified GHC.Driver.Session as GHC@@ -52,9 +58,11 @@ import qualified GHC.Unit.State                        as State import GHC.Driver.Env import GHC.Types.SrcLoc+import GHC.Settings (ToolSettings(..)) import Language.Haskell.Syntax.Module.Name import qualified Data.Foldable as Foldable import qualified GHC.Unit.Home.Graph as HUG+import qualified Data.Set as Set import Data.Maybe import GHC.Types.Target (InputFileBuffer) @@ -229,6 +237,17 @@       return (L.nubOrdOn targetTarget ctargets)     pure (hscEnv', concat ts) +-- | Find and return the ways in which the home units are built.+-- INVARIANT: All home units are built with the same 'Ways'+validateUnitsWays :: NonEmpty.NonEmpty (DynFlags, [GHC.Target]) -> IO Ways+validateUnitsWays flagsAndTargets = do+    let unitWays  = NonEmpty.map (ways . fst) flagsAndTargets+        firstWays = NonEmpty.head unitWays+        restWays  = NonEmpty.tail unitWays+    if all (== firstWays) restWays+        then return firstWays+        else error "Unexpected: the home units have different ways! Not supported, see GHC#26765"+ data TargetDetails = TargetDetails   { targetTarget :: Target   -- ^ Simplified version of 'TargetId', storing enough information@@ -404,6 +423,47 @@   backend = GHC.interpreterBackend #endif   }++-- | Enable the external interpreter by default unless the user sets+-- @preferInternalInterpreter=True@ (with @--internal-interpreter@)+enableExternalInterpreter :: Bool -> DynFlags -> DynFlags+enableExternalInterpreter preferInternalInterpreter dflags+  | preferInternalInterpreter+  = dflags `GHC.gopt_unset` GHC.Opt_ExternalInterpreter+  | otherwise+  = dflags `GHC.gopt_set` GHC.Opt_ExternalInterpreter++-- | Force -dynamic on the debuggee if the debugger (which is also the external+-- interpreter) was compiled with -dynamic. On Windows the debugger can't be+-- built dynamic, so we won't enable it there.+--+-- See Note [Dynamic Debuggee for dynamic debugger]+enableDynamicDebuggee :: DynFlags -> DynFlags+enableDynamicDebuggee dflags+  | hostIsDynamic+  = addWay' WayDyn dflags+  | otherwise+  = dflags++setPgmI, addOptI :: String -> DynFlags -> DynFlags+setPgmI f = alterToolSettings $ \s -> s { toolSettings_pgm_i = f }+addOptI f = alterToolSettings $ \s -> s { toolSettings_opt_i = f : toolSettings_opt_i s }++alterToolSettings :: (ToolSettings -> ToolSettings) -> DynFlags -> DynFlags+alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) }++setDynFlagWays :: Ways -> DynFlags -> DynFlags+setDynFlagWays ws dyn = Set.foldr addWay' dyn ws++addWay' :: Way -> DynFlags -> DynFlags+addWay' w dflags0 =+   let platform = targetPlatform dflags0+       dflags1 = dflags0 { targetWays_ = addWay w (targetWays_ dflags0) }+       dflags2 = foldr GHC.setGeneralFlag' dflags1+                       (wayGeneralFlags platform w)+       dflags3 = foldr GHC.unSetGeneralFlag' dflags2+                       (wayUnsetGeneralFlags platform w)+   in dflags3  -- ---------------------------------------------------------------------------- -- Utils that we need, but don't want to incur an additional dependency for.
haskell-debugger/GHC/Debugger/Session/Builtin.hs view
@@ -160,4 +160,3 @@ -- | GHC.Debugger.View.ByteString debuggerViewByteStringContents :: StringBuffer debuggerViewByteStringContents = stringToStringBuffer $(embedStringFile "haskell-debugger-view/src/GHC/Debugger/View/ByteString.hs")-
haskell-debugger/GHC/Debugger/Stopped.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,    DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,-   TypeApplications, ScopedTypeVariables, BangPatterns #-}+   TypeApplications, ScopedTypeVariables, BangPatterns, MultiWayIf #-} module GHC.Debugger.Stopped where  import Control.Monad import Control.Monad.Reader import Data.IORef-import GHC.Stack.CloneStack as Stack+import qualified Data.List as L  import GHC import GHC.Types.Unique.FM@@ -18,12 +18,13 @@ import GHC.Types.TypeEnv import GHC.Data.Maybe import GHC.Driver.Env as GHC-import GHC.Runtime.Debugger.Breakpoints as GHC import GHC.Runtime.Eval import GHC.Types.SrcLoc+import GHC.InfoProv import GHC.Utils.Outputable as Ppr import qualified GHC.Unit.Home.Graph as HUG +import GHC.Debugger.Stopped.Exception import GHC.Debugger.Stopped.Variables import GHC.Debugger.Runtime import GHC.Debugger.Runtime.Thread@@ -31,8 +32,10 @@ import GHC.Debugger.Runtime.Thread.Map import GHC.Debugger.Monad import GHC.Debugger.Interface.Messages+import qualified GHC.Debugger.Interface.Messages as DbgStackFrame (DbgStackFrame(..)) import GHC.Debugger.Utils-import qualified GHC.Debugger.Logger as Logger+import qualified Colog.Core as Logger+import qualified GHC.Stack.Types as Stack  {- Note [Don't crash if not stopped]@@ -87,6 +90,7 @@       = zipWith _mkDebuggeeThread t_ids t_labels    -- TODO: We ignore _all_threads and report only the main execution thread for now.+  -- See #138 for progress on Multi-threaded debugging.   GHC.getResumeContext >>= \case     [] ->       -- See Note [Don't crash if not stopped]@@ -110,109 +114,126 @@    tm <- liftIO . readIORef =<< asks threadMap   let m_f_tid = lookupThreadMap (remoteThreadIntRef req_tid) tm-  ipe_stack <- case m_f_tid of++  hsc_env <- getSession+  let hug = hsc_HUG hsc_env+  decoded_frames <- catMaybes <$> case m_f_tid of     Nothing -> pure []     Just f_tid -> do-      -- For now, assume that if we can get an IPE backtrace then this thread-      -- is compiled and doesn't have any interpreter frames.-      -- Of course, this isn't true since we should also be able to get an IPE-      -- backtrace for interpreter threads. Just not yet.-      ---      -- If IPE is empty => it's not an interpreter thread => the IPE backtrace-      --  is the best we can do, so just return that.-      getRemoteThreadIPEStack f_tid--  case ipe_stack of-    [] -> do-      _decoded_frames <- case m_f_tid of-        Nothing -> pure []-        Just f_tid -> do-          hsc_env <- getSession-          ibis <- getRemoteThreadStackCopy f_tid-          let hug = hsc_HUG hsc_env-          forM ibis $ \ibi -> do-            info_brks <- liftIO $ readIModBreaks hug ibi-            let modl  = getBreakSourceMod ibi info_brks-            srcSpan   <- liftIO $ getBreakLoc (readIModModBreaks hug) ibi info_brks--            -- Find function name-            -- TODO: Cache moduleLineMap?-            ticks <- fromMaybe (error "getStacktrace:getTicks") <$> makeModuleLineMap modl-            let current_toplevel_decl = enclosingTickSpan ticks srcSpan+      -- Try decoding a stack with interpreter continuation frames (RetBCOs)+      -- and use the BRK_FUN src locations.+      stack_frames <- getRemoteThreadStackCopy f_tid+      forM stack_frames $ \case+        StackFrameBreakpointInfo ibi -> do+          info_brks <- liftIO $ readIModBreaks hug ibi+          let modl  = getBreakSourceMod ibi info_brks+          srcSpan   <- liftIO $ getBreakLoc (readIModModBreaks hug) ibi info_brks+          decl <- liftIO $ L.intercalate "." <$> getBreakDecls (readIModModBreaks hug) ibi info_brks -            modl_str  <- display modl-            return DbgStackFrame-              { name = modl_str ++ "." -- ++ Stack.functionName stack_entry-              , sourceSpan = realSrcSpanToSourceSpan $ current_toplevel_decl -- realSrcSpan srcSpan+          modl_str  <- display modl+          return $ Just DbgStackFrame+            { name = modl_str ++ "." ++ decl+            , sourceSpan = realSrcSpanToSourceSpan $ realSrcSpan srcSpan+            , breakId = Just ibi+            }+        StackFrameIPEInfo ipe -> do+          case srcSpanStringToSourceSpan (ipLoc ipe) of+            Left err -> do+              -- Couldn't parse. The srcLoc may be invalid so just keep this as info, not warning.+              logSDoc Logger.Info $+                text "Couldn't parse StackEntry srcLoc \"" Ppr.<> text (ipLoc ipe)+                                                           Ppr.<> text "\":" <+> text err+              return Nothing+            Right sourceSpan ->+              return $ Just DbgStackFrame+                { name = ipMod ipe ++ "." ++ ipLabel ipe+                , sourceSpan = sourceSpan+                , breakId = Nothing+                }+        StackFrameAnnotation srcLoc ann -> do+            return $ Just DbgStackFrame+              { name = ann+              , sourceSpan = maybe unhelpfulSourceSpan srcLocToSourceSpan srcLoc+              , breakId = Nothing               } -      -- Try decoding a stack with interpreter continuation frames (RetBCOs) and use the BRK_FUN src locations.-      -- Add the latest resume context at the head.-      head_frame <- GHC.getResumeContext >>= \case-        [] ->-          -- See Note [Don't crash if not stopped]-          return Nothing-        r:_-          | Just ss <- srcSpanToRealSrcSpan (GHC.resumeSpan r)-          -> do-            r_tid <- getRemoteThreadIdFromRemoteContext (GHC.resumeContext r)-            if r_tid == req_tid then+  -- Add the latest resume context at the head.+  head_frame <- GHC.getResumeContext >>= \case+    [] ->+      -- See Note [Don't crash if not stopped]+      return Nothing+    r:_ -> do+      let resumeSpanR = GHC.resumeSpan r+          mRealSpan   = realSrcSpanToSourceSpan <$> srcSpanToRealSrcSpan resumeSpanR+          firstSpan   = DbgStackFrame.sourceSpan <$> listToMaybe decoded_frames+      r_tid <- getRemoteThreadIdFromRemoteContext (GHC.resumeContext r)+      if r_tid /= req_tid then+        return Nothing+      else case GHC.resumeBreakpointId r of+        Just ibi+          | Just ss <- mRealSpan+          , Just ss /= firstSpan -> do               -- We're getting the stacktrace for the thread we're stopped at.+              info_brks <- liftIO $ readIModBreaks hug ibi+              let modl  = getBreakSourceMod ibi info_brks+              modl_str  <- display modl               return $                 Just DbgStackFrame-                  { name = GHC.resumeDecl r-                  , sourceSpan = realSrcSpanToSourceSpan ss+                  { name = modl_str ++ "." ++ GHC.resumeDecl r+                  , sourceSpan = ss+                  , breakId = Just ibi                   }-            else-             return Nothing-          | otherwise ->-              -- No resume span; which should mean we're stopped on an exception.-              -- No info for now.-              return Nothing-      -- TODO: Currently, only display head_frame-      -- return (maybe id (:) head_frame $ decoded_frames)-      return (maybe [] (:[]) head_frame)-    ipe_frames -> catMaybes <$> do-      forM ipe_frames $ \stack_entry -> do-        case srcSpanStringToSourceSpan (Stack.srcLoc stack_entry) of-          Left err -> do-            -- Couldn't parse. The srcLoc may be invalid so just keep this as info, not warning.-            logSDoc Logger.Info $-              text "Couldn't parse StackEntry srcLoc \"" Ppr.<> text (Stack.srcLoc stack_entry)-                                                         Ppr.<> text "\":" <+> text err-            return Nothing-          Right sourceSpan ->-            return $ Just DbgStackFrame-              { name = Stack.moduleName stack_entry ++ "." ++ Stack.functionName stack_entry-              , sourceSpan = sourceSpan-              }+        _ -> do+          mExcSpan <- exceptionSourceSpanFromContext+          case mExcSpan of+            Just sourceSpan ->  return $ Just DbgStackFrame+                                  { name = GHC.resumeDecl r+                                  , sourceSpan+                                  , breakId = Nothing+                                  }+            Nothing -> return Nothing+  return (maybe id (:) head_frame $ decoded_frames) +srcLocToSourceSpan :: Stack.SrcLoc -> SourceSpan+srcLocToSourceSpan srcLoc =+  SourceSpan+    { file = Stack.srcLocFile srcLoc+    , startLine = Stack.srcLocStartLine srcLoc+    , endLine = Stack.srcLocEndLine srcLoc+    , startCol = Stack.srcLocStartCol srcLoc+    , endCol = Stack.srcLocEndCol srcLoc+    }+ -------------------------------------------------------------------------------- -- * 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+getScopes :: RemoteThreadId -> Int -> Debugger [ScopeInfo]+getScopes threadId frameIx = do+  frames <- getStacktrace threadId+  let frame = frames !! frameIx+  let sourceSpan = DbgStackFrame.sourceSpan frame+      localsScope = ScopeInfo+        { kind = LocalVariablesScope+        , expensive = False+        , numVars = Nothing+        , sourceSpan+        }+  if+    | frameIx < length frames+    , Just ibi <- DbgStackFrame.breakId frame     -> do+      hsc_env   <- getSession+      info_brks <- liftIO $ readIModBreaks (hsc_HUG hsc_env) ibi+      let brk_modl = getBreakSourceMod ibi info_brks       -- 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+      in_mod   <- getTopEnv brk_modl+      imported <- getTopImported brk_modl       return-        [ ScopeInfo { kind = LocalVariablesScope-                    , expensive = False-                    , numVars = Nothing-                    , sourceSpan-                    }+        [ localsScope         , ScopeInfo { kind = ModuleVariablesScope                     , expensive = True                     , numVars = Just (sizeUFM in_mod)@@ -225,10 +246,7 @@                     }         ]     | 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 []+      return [localsScope]  -------------------------------------------------------------------------------- -- * Variables@@ -248,102 +266,93 @@ -- (b) ONLY the variable requested -- (c) The fields of the variable requested but NOT the original variable --- | Get variables using a variable/variables reference+-- | 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@.+-- When the request forces a variable, return 'ForcedVariable'. Otherwise return+-- the resulting variables/fields. -- -- See Note [Variables Requests]-getVariables :: VariableReference -> Debugger (Either VarInfo [VarInfo])-getVariables vk = do+getVariables :: RemoteThreadId -> Int{-stack frame index-} -> VariableReference -> Debugger VariableResult+getVariables threadId frameIx vk = do+  frames <- getStacktrace threadId+  let frame = frames !! frameIx   hsc_env <- getSession-  GHC.getResumeContext >>= \case-    [] ->-      -- See Note [Don't crash if not stopped]-      return (Right [])-    r:_ -> case vk of+  case vk of+    -- Only `seq` the variable when inspecting a specific one (`SpecificVariable`)+    -- (VARR)(b,c)+    SpecificVariable key -> do+      term <- obtainTerm key -      -- 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 -            case term of+        -- (VARR)(b)+        Suspension{} -> do -              -- (VARR)(b)-              Suspension{} -> do+          -- Original Term was a suspension:+          -- It is a "lazy" DAP variable: our reply can ONLY include+          -- this single variable. -                -- Original Term was a suspension:-                -- It is a "lazy" DAP variable: our reply can ONLY include-                -- this single variable.+          term' <- forceTerm term -                term' <- forceTerm term+          vi <- termToVarInfo key term' -                vi <- termToVarInfo key term'+          return (ForcedVariable vi) -                return (Left vi)+        -- (VARR)(c)+        _ -> do -              -- (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 -                -- 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+            VarFields vfs -> pure (VariableFields vfs) -                termVarFields key term >>= \case-                  VarFields vfs -> return vfs+    -- (VARR)(a) from here onwards -      -- (VARR)(a) from here onwards+    LocalVariables -> fmap VariableFields $ do+      -- bindLocalsAtBreakpoint hsc_env (GHC.resumeApStack r) (GHC.resumeSpan r) (GHC.resumeBreakpointId r)+      mapM tyThingToVarInfo =<< GHC.getBindings -      LocalVariables -> fmap Right $ do-        -- bindLocalsAtBreakpoint hsc_env (GHC.resumeApStack r) (GHC.resumeSpan r) (GHC.resumeBreakpointId r)-        mapM tyThingToVarInfo =<< GHC.getBindings+    ModuleVariables+      | frameIx < length frames+      , Just ibi <- DbgStackFrame.breakId frame+      -> fmap VariableFields $ do+        curr_modl <- liftIO $ getBreakSourceMod ibi <$>+                      readIModBreaks (hsc_HUG hsc_env) ibi+        things <- typeEnvElts <$> getTopEnv curr_modl+        mapM (\tt -> do+          nameStr <- display (getName tt)+          vi <- tyThingToVarInfo tt+          return vi{varName = nameStr}) things -      ModuleVariables -> Right <$> do-        case GHC.resumeBreakpointId r of-          Nothing -> return []-          Just ibi -> do-            curr_modl <- liftIO $ getBreakSourceMod ibi <$>-                          readIModBreaks (hsc_HUG hsc_env) ibi-            things <- typeEnvElts <$> getTopEnv curr_modl-            mapM (\tt -> do-              nameStr <- display (getName tt)+    GlobalVariables+      | frameIx < length frames+      , Just ibi <- DbgStackFrame.breakId frame+      -> fmap VariableFields $ do+        curr_modl <- liftIO $ getBreakSourceMod ibi <$>+                      readIModBreaks (hsc_HUG hsc_env) ibi+        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}) things+              return vi{varName = nameStr}+          ) names -      GlobalVariables -> Right <$> do-        case GHC.resumeBreakpointId r of-          Nothing -> return []-          Just ibi -> do-            curr_modl <- liftIO $ getBreakSourceMod ibi <$>-                          readIModBreaks (hsc_HUG hsc_env) ibi-            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 -> pure (VariableFields []) -      NoVariables -> Right <$> do-        return []+    -- Couldn't find ibi or frame+    _otherwise -> pure (VariableFields [])  -------------------------------------------------------------------------------- -- Inspect
+ haskell-debugger/GHC/Debugger/Stopped/Exception.hs view
@@ -0,0 +1,219 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultilineStrings #-}++-- | Helpers used when the debugger is stopped due to an exception.+-- These helpers execute code on the remote process which teach us information+-- about the exception we are stopped at.+module GHC.Debugger.Stopped.Exception+  ( exceptionSourceSpanFromContext+  , getExceptionInfo+  , fallbackExceptionSourceSpan+  , defaultExceptionInfo+  , currentlyStoppedOnException+  ) where++import Data.Maybe (fromMaybe, isNothing)++import GHC+import GHC.Types.SrcLoc+import GHC.Data.FastString (unpackFS)+import GHC.Utils.Outputable as Ppr++import GHC.Debugger.Monad+import GHC.Debugger.Interface.Messages+  ( SourceSpan(..)+  , ExceptionInfo(..)+  , RemoteThreadId(..)+  )+import qualified Colog.Core as Logger+import GHC.Debugger.Runtime.Thread+import qualified GHC.Debugger.Runtime.Eval.RemoteExpr as Remote+import GHC.Debugger.Runtime.Term.Parser+import GHCi.RemoteTypes (castForeignRef)+import GHC.Builtin.Types (anyTy)++-- | Attempt to obtain a more precise 'SourceSpan' for the exception we stopped+-- on by consulting its context, if available.+exceptionSourceSpanFromContext :: Debugger (Maybe SourceSpan)+exceptionSourceSpanFromContext = do+  GHC.getResumeContext >>= \case+    r:_ | Nothing <- GHC.resumeBreakpointId r -> do+      let excRef = resumeApStack r+      evalRes <- Remote.eval+        (Remote.raw exceptionLocationExpr `Remote.app` Remote.untypedRef excRef)+      case evalRes of+        Left err -> do+          logSDoc Logger.Debug $+            Ppr.text "Failed to evaluate exception context:" Ppr.<+> Ppr.text (show err)+          return Nothing+        Right fhv -> do+          parsed <- obtainParsedTerm "Exception context location" 4 True anyTy (castForeignRef fhv)+            (maybeParser exceptionLocationTupleParser)+          case parsed of+            Left errs -> do+              logSDoc Logger.Debug $+                Ppr.text "Failed to parse exception context location:"+                  Ppr.<+> Ppr.vcat (map (Ppr.text . getTermErrorMessage) errs)+              return Nothing+            Right Nothing -> return Nothing+            Right (Just (file, srcLine, col)) ->+              return $ Just SourceSpan+                { file = file+                , startLine = srcLine+                , startCol = col+                , endLine = srcLine+                , endCol = col+                }+    _ -> return Nothing++exceptionLocationTupleParser :: TermParser (String, Int, Int)+exceptionLocationTupleParser =+  (,,) <$> subtermWith 0 stringParser+       <*> subtermWith 1 intParser+       <*> subtermWith 2 intParser++-- | This helper looks at the exception context for an exception, and retrieves+-- the last entry of the HasCallStack backtrace.+exceptionLocationExpr :: String+exceptionLocationExpr ="""+  let+    fromCallStack cs = case Data.Maybe.listToMaybe (GHC.Exception.getCallStack cs) of+      Just (_, loc) -> Just ( GHC.Exception.srcLocFile loc+                            , GHC.Exception.srcLocStartLine loc+                            , GHC.Exception.srcLocStartCol loc)+    go exc =+      let ctx = Control.Exception.someExceptionContext exc+          bts :: [Control.Exception.Backtrace.Backtraces]+          bts = Control.Exception.Context.getExceptionAnnotations ctx+      in case bts of+           bt : _ -> case GHC.Internal.Exception.Backtrace.btrHasCallStack bt of+             Just cs -> fromCallStack cs+             Nothing -> Nothing+           [] -> Nothing+  in go+"""++-- | Retrieve structured exception information for the requested thread when+-- the debugger is currently stopped on an exception.+getExceptionInfo :: RemoteThreadId -> Debugger ExceptionInfo+getExceptionInfo req_tid = GHC.getResumeContext >>= \case+  [] -> return defaultExceptionInfo+  r:_ -> do+    r_tid <- getRemoteThreadIdFromRemoteContext (GHC.resumeContext r)+    case (r_tid == req_tid, GHC.resumeBreakpointId r) of+      (True, Nothing) -> do+        let excRef = resumeApStack r+        fromMaybe defaultExceptionInfo <$> exceptionInfoFromContext excRef+      _ -> return defaultExceptionInfo++-- | Evaluate helper code inside the debuggee that turns the exception context+-- into our 'ExceptionInfo' structure.+exceptionInfoFromContext :: ForeignHValue -> Debugger (Maybe ExceptionInfo)+exceptionInfoFromContext excRef = do+  -- 1. Add a "data" declaration for the datatype the expression will return+  _ <- runDecls exceptionInfoData+  -- 2. Gather information about the exception.+  evalRes <- Remote.eval+    (Remote.raw exceptionInfoExpr `Remote.app` Remote.untypedRef excRef)+  case evalRes of+    Left err -> do+      logSDoc Logger.Debug $+        Ppr.text "Failed to evaluate exception info:" Ppr.<+> Ppr.text (show err)+      return Nothing+    Right fhv -> do+      parsed <- obtainParsedTerm "Exception info" 4 True anyTy (castForeignRef fhv)+        exceptionInfoParser+      case parsed of+        Left errs -> do+          logSDoc Logger.Debug $+            Ppr.text "Failed to parse exception info:"+              Ppr.<+> Ppr.vcat (map (Ppr.text . getTermErrorMessage) errs)+          return Nothing+        Right info -> return (Just info)++-- | Parse the helper 'ExceptionInfoNode' structure produced inside the+-- debuggee into our externally facing 'ExceptionInfo'.+exceptionInfoParser :: TermParser ExceptionInfo+exceptionInfoParser = do+  ExceptionInfo+    <$> subtermWith 0 stringParser+    <*> subtermWith 1 stringParser+    <*> subtermWith 2 stringParser+    <*> subtermWith 3 (maybeParser stringParser)+    <*> subtermWith 4 (parseList exceptionInfoParser)++-- | Definition for the helper 'ExceptionInfoNode' data type compiled into the+-- debuggee to aid in transporting nested exception information.+-- We need a specific datatype because ExceptionInfoNode is recursive.+exceptionInfoData :: String+exceptionInfoData =+  "data ExceptionInfoNode = ExceptionInfoNode String String String (Maybe String) [ExceptionInfoNode]"++-- | Helper expression run in the debuggee that walks the exception context and+-- populates the 'ExceptionInfoNode' structure.+exceptionInfoExpr :: String+exceptionInfoExpr = """+  let collectExceptionInfo :: SomeException -> ExceptionInfoNode+      collectExceptionInfo se' =+        case se' of+          SomeException exc ->+            let ctx = Control.Exception.someExceptionContext se'+                rendered = Control.Exception.Context.displayExceptionContext ctx+                whileHandling = Control.Exception.Context.getExceptionAnnotations ctx+                innerNodes = map (collectExceptionInfo . unwrap) whileHandling+                simpleTypeName = Data.Typeable.tyConName tc+                modulePrefix = case Data.Typeable.tyConModule tc of+                  mdl | null mdl -> \"\"+                      | otherwise -> mdl ++ \".\"+                packagePrefix = case Data.Typeable.tyConPackage tc of+                  pkg | null pkg -> \"\"+                      | otherwise -> pkg ++ \":\"+                tc = Data.Typeable.typeRepTyCon (Data.Typeable.typeOf exc)+                fullTypeName = packagePrefix ++ modulePrefix ++ simpleTypeName+                unwrap (Control.Exception.WhileHandling inner) = inner+                contextText = if null rendered then Nothing else Just rendered+            in ExceptionInfoNode+                 simpleTypeName+                 fullTypeName+                 (Control.Exception.displayException se')+                 contextText+                 innerNodes+  in collectExceptionInfo+  """+++-- | When no precise exception location is available, fall back to displaying a+-- label derived from the provided 'SrcSpan'.+fallbackExceptionSourceSpan :: Maybe SrcSpan -> SourceSpan+fallbackExceptionSourceSpan mspan =+  let fileLabel = maybe "<exception>" spanLabel mspan+  in SourceSpan+       { file = fileLabel+       , startLine = 0+       , startCol = 0+       , endLine = 0+       , endCol = 0+       }+  where+    spanLabel (RealSrcSpan rss _) = unpackFS (srcSpanFile rss)+    spanLabel (UnhelpfulSpan reason) = unpackFS (unhelpfulSpanFS reason)++-- | Placeholder exception info returned when the context could not be+-- inspected.+defaultExceptionInfo :: ExceptionInfo+defaultExceptionInfo = ExceptionInfo+  { exceptionInfoTypeName = "Exception"+  , exceptionInfoFullTypeName = "Exception"+  , exceptionInfoMessage = "Exception information not available"+  , exceptionInfoContext = Nothing+  , exceptionInfoInner = []+  }++-- | Determine whether the debugger is currently stopped because of an+-- exception (as opposed to a breakpoint).+currentlyStoppedOnException :: Debugger Bool+currentlyStoppedOnException = do+  resumes <- GHC.getResumeContext+  return $ case resumes of+    [] -> False+    r:_ -> isNothing (GHC.resumeBreakpointId r)
haskell-debugger/GHC/Debugger/Stopped/Variables.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE CPP, NamedFieldPuns, TupleSections, LambdaCase,    DuplicateRecordFields, RecordWildCards, TupleSections, ViewPatterns,-   TypeApplications, ScopedTypeVariables, BangPatterns, DerivingVia, TypeAbstractions #-}+   TypeApplications, ScopedTypeVariables, BangPatterns, DerivingVia,+   TypeAbstractions, DataKinds #-} module GHC.Debugger.Stopped.Variables where  import Control.Monad.Reader@@ -65,12 +66,14 @@           -- Not a record type,           -- Use indexed fields           [] -> do-            let keys = zipWith (\ix _ -> FromPath top_key (PositionalIndex ix)) [1..] (dataConOrigArgTys dc)+            let keys = zipWith (\ix _ -> FromPath top_key (PositionalIndex ix)) [1..] (dataConRepArgTys dc)             VarFields <$> 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+            let mkPath ix Nothing = FromPath top_key (PositionalIndex ix)+                mkPath ix (Just fld) = FromPath top_key (LabeledField ix (flSelector fld))+                keys = zipWith mkPath [1..] ((Nothing <$ dataConOtherTheta dc) ++ (map Just dataConFields))             VarFields <$> 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@@ -79,7 +82,7 @@             wvi <- obtainTerm key >>= termToVarInfo key             return (VarFields [wvi])           [fld] -> do-            let key = FromPath top_key (LabeledField (flSelector fld))+            let key = FromPath top_key (LabeledField 1 (flSelector fld))             wvi <- obtainTerm key >>= termToVarInfo key             return (VarFields [wvi])           _ -> error "unexpected number of Newtype fields: larger than 1"@@ -109,7 +112,6 @@   case term0 of     -- The simple case: The term is a a thunk...     Suspension{} -> do-      ir <- getVarReference key       return VarInfo         { varName         , varType@@ -118,7 +120,7 @@             else "_"         , varRef = if isFn             then NoVariables-            else SpecificVariable ir -- allows forcing the thunk+            else SpecificVariable key -- allows forcing the thunk         , isThunk = not isFn         } @@ -150,8 +152,7 @@           varRef <- do             if hasDirectSubTerms term0              then do-                ir <- getVarReference key-                return (SpecificVariable ir)+                return (SpecificVariable key)              else do                 return NoVariables @@ -165,8 +166,7 @@           varRef <-             if varExpandable             then do-                ir <- getVarReference key-                return (SpecificVariable ir)+                return (SpecificVariable key)              else do                 return NoVariables           return VarInfo
haskell-debugger/GHC/Debugger/Utils.hs view
@@ -5,11 +5,13 @@   ( module GHC.Debugger.Utils   , module GHC.Utils.Outputable   , module GHC.Utils.Trace+  , showSDoc   ) where +import Control.Monad import Control.Applicative-import Control.Monad.IO.Class import Control.Exception+import System.IO  import GHC import GHC.Data.FastString@@ -18,14 +20,35 @@ import GHC.Utils.Outputable hiding (char) import GHC.Utils.Trace import qualified Data.Text as T+import qualified Data.Text.IO as T  import Data.Attoparsec.Text -import GHC.Debugger.Monad-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger import GHC.Debugger.Interface.Messages  --------------------------------------------------------------------------------+-- * Handle utils+--------------------------------------------------------------------------------++-- | Read output from the given handle and write it to the given+-- log action (forever).+forwardHandleToLogger :: Handle -> LogAction IO T.Text -> IO ()+forwardHandleToLogger read_h logger = do+  forwarding `catch` -- handles read EOF+    \(_e::SomeException) -> do+      -- Cleanly exit on exception+      -- print _e+      return ()+  where+    forwarding = forever $ do+      -- Mask exceptions to avoid being killed between reading+      -- a line and outputting it.+      mask_ $ do+        out_line <- T.hGetLine read_h -- See Note [External interpreter buffering]+        logger <& out_line++-------------------------------------------------------------------------------- -- * GHC Utilities -------------------------------------------------------------------------------- @@ -40,23 +63,11 @@   }  -- | Display an Outputable value as a String-display :: Outputable a => a -> Debugger String+display :: (GhcMonad m, Outputable a) => a -> m String display x = do   dflags <- getDynFlags   return $ showSDoc dflags (ppr x) {-# INLINE display #-}------------------------------------------------------------------------------------- * More utils-----------------------------------------------------------------------------------expectRight :: Exception e => Either e a -> Debugger a-expectRight s = case s of-  Left e -> do-    logSDoc Logger.Error (text $ displayException e)-    liftIO $ throwIO e-  Right a -> do-    pure a  -------------------------------------------------------------------------------- -- * Parsing
hdb/Development/Debug/Adapter.hs view
@@ -5,12 +5,13 @@ import qualified Data.IntSet as IS import qualified Data.ByteString as BS import qualified Data.Map as Map+import qualified Data.IntMap as IM import qualified Data.Text as T import System.FilePath  import DAP import qualified GHC-import qualified GHC.Debugger.Interface.Messages as D (Command, Response)+import qualified GHC.Debugger.Interface.Messages as D (Command, Response, RemoteThreadId, VariableReference)  type DebugAdaptor = Adaptor DebugAdaptorState Request type DebugAdaptorCont = Adaptor DebugAdaptorState ()@@ -25,8 +26,10 @@ data DebugAdaptorState = DAS       { syncRequests :: MVar D.Command       , syncResponses :: MVar D.Response-      , nextFreshBreakpointId :: !BreakpointId+      , nextFreshId :: !Int       , breakpointMap :: Map.Map GHC.InternalBreakpointId BreakpointSet+      , stackFrameMap :: IM.IntMap StackFrameIx+      , variablesMap  :: IM.IntMap VariablesIx       , entryFile :: FilePath       , entryPoint :: String       , entryArgs :: [String]@@ -42,6 +45,10 @@ type BreakpointId = Int type BreakpointSet = IS.IntSet +data StackFrameIx = StackFrameIx D.RemoteThreadId Int{-stack frame ix-}+  deriving (Eq, Ord)+data VariablesIx = VariablesIx StackFrameIx D.VariableReference+ instance MonadFail DebugAdaptor where   fail a = error a   -- TODO: PROPER ERROR HANDLING with termination, possibly delete this instance@@ -63,3 +70,9 @@      else return (root </> file)   return defaultSource{sourcePath = Just (T.pack fullPath)} +-- | Generate fresh Int identifier.+getFreshId :: DebugAdaptor Int+getFreshId = do+  nid <- nextFreshId <$> getDebugSession+  updateDebugSession $ \s -> s { nextFreshId = nextFreshId s + 1 }+  pure nid
hdb/Development/Debug/Adapter/Breakpoints.hs view
@@ -141,18 +141,11 @@         , DAP.breakpointId = Just bid         }) bids --- | Adds new BreakpointId for a givent StgPoint+-- | Adds new BreakpointId to the debug adapter mapping registerNewBreakpoint :: GHC.InternalBreakpointId -> DebugAdaptor BreakpointId registerNewBreakpoint breakpoint = do-  bkpId <- getFreshBreakpointId+  bkpId <- getFreshId   updateDebugSession $ \das@DAS{..} -> das {breakpointMap = Map.insertWith mappend breakpoint (IS.singleton bkpId) breakpointMap}-  pure bkpId---- | Generate fresh breakpoint Id.-getFreshBreakpointId :: DebugAdaptor BreakpointId-getFreshBreakpointId = do-  bkpId <- nextFreshBreakpointId <$> getDebugSession-  updateDebugSession $ \s -> s { nextFreshBreakpointId = nextFreshBreakpointId s + 1 }   pure bkpId  -- | Get the file from a DAP Source
hdb/Development/Debug/Adapter/Evaluation.hs view
@@ -4,6 +4,7 @@ import qualified Data.Text as T import qualified Data.Map as M import qualified Data.IntSet as IS+import qualified Data.IntMap as IM  import DAP @@ -36,7 +37,7 @@ -- | Command for evaluation (includes evaluation-on-hover) commandEvaluate :: DebugAdaptor () commandEvaluate = do-  EvaluateArguments {evaluateArgumentsFrameId=_todo, ..} <- getArguments+  EvaluateArguments {evaluateArgumentsFrameId=_todo{-evaluate expression in specific frame-}, ..} <- getArguments    let simpleEvalResp res ty = EvaluateResponse         { evaluateResponseResult             = res@@ -57,11 +58,26 @@     EvalException {resultVal, resultType} ->       sendEvaluateResponse (simpleEvalResp (T.pack resultVal) (T.pack resultType))     EvalCompleted{resultVal, resultType, resultStructureRef} -> do+      varIx <- case resultStructureRef of+        NoVariables     -> pure 0+        LocalVariables  -> error "Impossible! Eval result ref should always be NoVariables or SpecificVariable"+        ModuleVariables -> error "Impossible! Eval result ref should always be NoVariables or SpecificVariable"+        GlobalVariables -> error "Impossible! Eval result ref should always be NoVariables or SpecificVariable"+        SpecificVariable _ -> do+          varId <- getFreshId+          updateDebugSession $ \s ->+            s { variablesMap =+                  IM.insert varId+                    (VariablesIx (StackFrameIx (RemoteThreadId (-1)) (-1) {- shouldn't be a problem bc it's not Local/Module/Global vars -}) resultStructureRef)+                    s.variablesMap+              }+          pure varId+       sendEvaluateResponse EvaluateResponse         { evaluateResponseResult             = T.pack resultVal         , evaluateResponseType               = T.pack resultType         , evaluateResponsePresentationHint   = Nothing-        , evaluateResponseVariablesReference = fromEnum resultStructureRef+        , evaluateResponseVariablesReference = varIx         , evaluateResponseNamedVariables     = Nothing         , evaluateResponseIndexedVariables   = Nothing         , evaluateResponseMemoryReference    = Nothing
+ hdb/Development/Debug/Adapter/ExceptionInfo.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+module Development.Debug.Adapter.ExceptionInfo+  ( commandExceptionInfo+  ) where++import qualified Data.Text as T++import DAP++import Development.Debug.Adapter+import Development.Debug.Adapter.Interface+import qualified GHC.Debugger.Interface.Messages as D++commandExceptionInfo :: DebugAdaptor ()+commandExceptionInfo = do+  ExceptionInfoArguments{..} <- getArguments+  let remoteThread = D.RemoteThreadId exceptionInfoArgumentsThreadId+  D.GotExceptionInfo info <- sendSync (D.GetExceptionInfo remoteThread)+  sendExceptionInfoResponse (toDAPExceptionInfo info)++-- | Convert the debugger's 'ExceptionInfo' into a DAP 'ExceptionInfoResponse'.+toDAPExceptionInfo :: D.ExceptionInfo -> ExceptionInfoResponse+toDAPExceptionInfo info =+  let typeNameStr = exceptionTypeName info+      typeNameText = T.pack typeNameStr+      messageStr = exceptionMessage info+      messageText = T.pack <$> messageStr+  in ExceptionInfoResponse+      { exceptionInfoResponseExceptionId = typeNameText+      , exceptionInfoResponseDescription = messageText+      , exceptionInfoResponseBreakMode = Always+      , exceptionInfoResponseDetails = Just (exceptionInfoToDetails (Just "_exception") info)+      }++exceptionInfoToDetails :: Maybe T.Text -> D.ExceptionInfo -> ExceptionDetails+exceptionInfoToDetails evalName info@D.ExceptionInfo{..} =+  let typeNameText = T.pack (exceptionTypeName info)+      fullTypeNameText = T.pack (exceptionFullTypeName info)+      stackTraceText = T.pack <$> exceptionInfoContext+      innerDetails = map (exceptionInfoToDetails Nothing) exceptionInfoInner+      innerField = if null innerDetails then Nothing else Just innerDetails+  in defaultExceptionDetails+        { exceptionDetailsMessage = exceptionMessage info+        , exceptionDetailstypeName = Just typeNameText+        , exceptionDetailsFullTypeName = Just fullTypeNameText+        , exceptionDetailsStackTrace = stackTraceText+        , exceptionDetailsInnerException = innerField+        , exceptionDetailsEvaluateName = evalName+        }++exceptionTypeName :: D.ExceptionInfo -> String+exceptionTypeName D.ExceptionInfo{..}+  | null exceptionInfoTypeName = "Exception"+  | otherwise = exceptionInfoTypeName++exceptionFullTypeName :: D.ExceptionInfo -> String+exceptionFullTypeName info@D.ExceptionInfo{..}+  | null exceptionInfoFullTypeName = exceptionTypeName info+  | otherwise = exceptionInfoFullTypeName++exceptionMessage :: D.ExceptionInfo -> Maybe String+exceptionMessage D.ExceptionInfo{..}+  | null exceptionInfoMessage = Nothing+  | otherwise = Just exceptionInfoMessage
hdb/Development/Debug/Adapter/Handles.hs view
@@ -9,7 +9,6 @@  import DAP - import System.IO () import DAP.Log import qualified Data.Text as T@@ -21,16 +20,6 @@ import Control.Exception import Control.Monad import Control.Concurrent.Async--{- Note [Debugger Handles]-~~~~~~~~~~~~~~~~~~~~~~~~~~--The debugger executes arbritary user programs which will output to stdout and stderr.-This output needs to be sent to the debug client. Any output we wish to put in a-log or elsewhere needs to be directed to another handle.----}  handleLogger :: Handle -> IO (LogAction IO T.Text) handleLogger out_handle = do
hdb/Development/Debug/Adapter/Init.hs view
@@ -32,12 +32,15 @@ import GHC.Generics import System.Directory import System.FilePath+import Data.Functor.Contravariant  import Development.Debug.Adapter import Development.Debug.Adapter.Exit-import GHC.Debugger.Logger as Logger+import Colog.Core as Logger import qualified Development.Debug.Adapter.Output as Output +import GHC.Utils.Logger (defaultLogActionWithHandles)+import GHC.Debugger.Utils (forwardHandleToLogger) import qualified GHC.Debugger as Debugger import qualified GHC.Debugger.Monad as Debugger import qualified GHC.Debugger.Interface.Messages as D (Command, Response)@@ -48,21 +51,6 @@ import Development.Debug.Session.Setup  ----------------------------------------------------------------------------------- * Logging-----------------------------------------------------------------------------------data InitLog-  = DebuggerLog Debugger.DebuggerLog-  | DebuggerMonadLog Debugger.DebuggerMonadLog-  | FlagsLog FlagsLog--instance Pretty InitLog where-  pretty = \ case-    DebuggerLog msg -> pretty msg-    DebuggerMonadLog msg -> pretty msg-    FlagsLog msg -> pretty msg---------------------------------------------------------------------------------- -- * Client -------------------------------------------------------------------------------- @@ -86,17 +74,27 @@     deriving anyclass FromJSON  --------------------------------------------------------------------------------+-- * Logging+--------------------------------------------------------------------------------++data DAPLog+  = DAPSessionSetupLog (WithSeverity SessionSetupLog)+  | DAPDebuggerLog Debugger.DebuggerLog++-------------------------------------------------------------------------------- -- * Launch Debugger -------------------------------------------------------------------------------- + -- | Exception type for when hie-bios initialization fails newtype InitFailed = InitFailed String deriving Show  -- | Initialize debugger -- -- Returns @()@ if successful, throws @InitFailed@ otherwise-initDebugger :: Recorder (WithSeverity InitLog) -> Bool -> LaunchArgs -> ExceptT InitFailed DebugAdaptor ()-initDebugger l supportsRunInTerminal+initDebugger :: LogAction IO DAPLog -> Bool -> Bool+             -> LaunchArgs -> ExceptT InitFailed DebugAdaptor ()+initDebugger l supportsRunInTerminal preferInternalInterpreter                LaunchArgs{ __sessionId                          , projectRoot = givenRoot                          , entryFile = entryFileMaybe@@ -116,40 +114,65 @@    projectRoot <- maybe (liftIO getCurrentDirectory) pure givenRoot -  let hieBiosLogger = cmapWithSev FlagsLog l+  let hieBiosLogger = contramap DAPSessionSetupLog l   liftIO (runExceptT (hieBiosSetup hieBiosLogger projectRoot entryFile)) >>= \case     Left e              -> throwError $ InitFailed e     Right (Left e)      -> lift       $ exitWithMsg e     Right (Right flags) -> do-      let nextFreshBreakpointId = 0+      -- Create the stdin handle for the external interpreter+      (readExternalIntStdin, writeExternalIntStdin) <- liftIO P.createPipe+      liftIO $ do+        hSetBuffering readExternalIntStdin LineBuffering+        hSetBuffering writeExternalIntStdin NoBuffering+        hSetEncoding readExternalIntStdin utf8+        hSetEncoding writeExternalIntStdin utf8+      when (not preferInternalInterpreter) $ liftIO $ void $ do+        -- forward proxy in to external interpreter stdin+        forkIO $ forever $ do+          i <- readChan syncProxyIn+          -- 3. Write to write-end of the pipe+          BS.hPut writeExternalIntStdin i >> hFlush writeExternalIntStdin++      let nextFreshId = 0           breakpointMap = mempty+          stackFrameMap = mempty+          variablesMap  = mempty           defaultRunConf = Debugger.RunDebuggerSettings             { supportsANSIStyling = True     -- TODO: Initialize Request sends supportsANSIStyling; this is False for nvim-dap             , supportsANSIHyperlinks = False -- VSCode does not support this+            , preferInternalInterpreter+            , externalInterpreterStdinStream = UseHandle readExternalIntStdin             } -      -- Create pipes to read/write the debugger (not debuggee's) output.-      -- The write end is given to `runDebugger` and the read end is continuously-      -- read from until we read an EOF.-      (readDebuggerOutput, writeDebuggerOutput) <- liftIO P.createPipe+      -- Create a pipe to which messages to send to the DAP console are written and read.+      -- todo: This could just be a Haskell channel now...+      (readDAPOutput, writeDAPOutput) <- liftIO P.createPipe       liftIO $ do-        hSetBuffering readDebuggerOutput LineBuffering-        hSetBuffering writeDebuggerOutput NoBuffering+        hSetBuffering readDAPOutput LineBuffering+        hSetBuffering writeDAPOutput NoBuffering         -- GHC output uses utf8-        hSetEncoding readDebuggerOutput utf8-        hSetEncoding writeDebuggerOutput utf8+        hSetEncoding readDAPOutput utf8+        hSetEncoding writeDAPOutput utf8         setLocaleEncoding utf8        finished_init <- liftIO $ newEmptyMVar +      dbgLog <- liftIO $+        createDebuggerLogger l writeDAPOutput (supportsRunInTerminal, syncProxyOut, syncProxyErr)+       let absEntryFile = normalise $ projectRoot </> entryFile       lift $ registerNewDebugSession (maybe "debug-session" T.pack __sessionId) DAS{entryFile=absEntryFile,..}-        [ debuggerThread l finished_init writeDebuggerOutput projectRoot flags+        [ debuggerThread dbgLog finished_init projectRoot flags             extraGhcArgs absEntryFile defaultRunConf syncRequests syncResponses-        , handleDebuggerOutput readDebuggerOutput-        , stdinForwardThread  supportsRunInTerminal syncProxyIn-        , stdoutCaptureThread supportsRunInTerminal syncProxyOut-        , stderrCaptureThread supportsRunInTerminal syncProxyErr++        , \withAdaptor -> forwardHandleToLogger readDAPOutput $+            LogAction (\msg -> withAdaptor (Output.neutral msg))++        -- Setup capturing of the process' own stdout and forwarding of the process' own stdin,+        -- but only if we're using the internal interpreter!+        , if preferInternalInterpreter then stdinForwardThread  supportsRunInTerminal syncProxyIn  else mempty+        , if preferInternalInterpreter then stdoutCaptureThread supportsRunInTerminal syncProxyOut else mempty+        , if preferInternalInterpreter then stderrCaptureThread supportsRunInTerminal syncProxyErr else mempty         ]        -- Do not return until the initialization is finished@@ -160,65 +183,7 @@           -- This can happen if compilation fails and the compiler exits cleanly.           --           -- Instead of signalInitialized, respond with error and exit.-          lift $ exitCleanupWithMsg readDebuggerOutput e---- | This thread captures stdout from the debuggee and sends it to the client.--- NOTE, redirecting the stdout handle is a process-global operation. So this thread--- will capture ANY stdout the debuggee emits. Therefore you should never directly--- write to stdout, but always write to the appropiate handle.-stdoutCaptureThread :: Bool -> Chan BS.ByteString -> (DebugAdaptorCont () -> IO ()) -> IO ()-stdoutCaptureThread runInTerminal syncOut withAdaptor = do-  tid <- myThreadId-  labelThread tid "Stdin Capture Thread"-  withInterceptedStdout $ \_ interceptedStdout -> do-    forever $ do-      line <- liftIO $ T.hGetLine interceptedStdout-      when runInTerminal $-        writeChan syncOut $ T.encodeUtf8 (line <> T.pack "\n")--      -- Always output to Debug Console-      withAdaptor $ Output.stdout line---- | Like 'stdoutCaptureThread' but for stderr-stderrCaptureThread :: Bool -> Chan BS.ByteString -> (DebugAdaptorCont () -> IO ()) -> IO ()-stderrCaptureThread runInTerminal syncErr withAdaptor = do-  tid <- myThreadId-  labelThread tid "Stderr Capture Thread"-  withInterceptedStderr $ \_ interceptedStderr -> do-    forever $ do-      line <- liftIO $ T.hGetLine interceptedStderr-      when runInTerminal $-        writeChan syncErr $ T.encodeUtf8 (line <> "\n")--      -- Always output to Debug Console-      catch-        (withAdaptor $ Output.stderr line)-        (\(_ :: SomeException) ->-          throwIO (FailedToWriteToAdaptor line))--newtype FailedToWriteToAdaptor = FailedToWriteToAdaptor T.Text-instance Show FailedToWriteToAdaptor where-  show (FailedToWriteToAdaptor t) = "Failed to write to debug adapter: " ++ T.unpack t-instance Exception FailedToWriteToAdaptor--stdinForwardThread :: Bool -> Chan BS.ByteString -> (DebugAdaptorCont () -> IO ()) -> IO ()-stdinForwardThread runInTerminal syncIn _withAdaptor = do-  tid <- myThreadId-  labelThread tid "Stdin Forward Thread"-  when runInTerminal $ do-    -- We need to hijack stdin to write to it--    -- 1. Create a new pipe from writeEnd->readEnd-    (readEnd, writeEnd) <- createPipe--    -- 2. Substitute the read-end of the pipe by stdin-    _ <- hDuplicateTo readEnd stdin-    hClose readEnd -- we'll never need to read from readEnd--    forever $ do-      i <- readChan syncIn-      -- 3. Write to write-end of the pipe-      BS.hPut writeEnd i >> hFlush writeEnd+          lift $ exitCleanupWithMsg readDAPOutput e  -- | The main debugger thread launches a GHC.Debugger session. --@@ -237,9 +202,8 @@ --    This sets the global CWD for this process, disallowing multiple sessions --    at the same, but that's OK because we currently only support --    single-session mode. Each new session gets a new debugger process.-debuggerThread :: Recorder (WithSeverity InitLog)+debuggerThread :: LogAction IO Debugger.DebuggerLog                -> MVar (Either String ()) -- ^ To signal when initialization is complete.-               -> Handle          -- ^ The write end of a handle for debug compiler output                -> FilePath        -- ^ Working directory for GHC session                -> HieBiosFlags    -- ^ GHC Invocation flags                -> [String]        -- ^ Extra ghc args@@ -250,7 +214,7 @@                -> (DebugAdaptorCont () -> IO ())                -- ^ Allows unlifting DebugAdaptor actions to IO. See 'registerNewDebugSession'.                -> IO ()-debuggerThread recorder finished_init writeDebuggerOutput workDir HieBiosFlags{..} extraGhcArgs mainFp runConf requests replies withAdaptor = do+debuggerThread l finished_init workDir HieBiosFlags{..} extraGhcArgs mainFp runConf requests replies withAdaptor = do    -- See Notes (CWD) above   setCurrentDirectory workDir@@ -264,20 +228,14 @@    catches     (do-      -- The logger should log things starting from Info to the debugger output-      -- handle as well, so the user sees it in the Console.-      debugAdapterLogger <- handleLogger writeDebuggerOutput-      let final_logger = cmapWithSev DebuggerMonadLog recorder <>-                         applyVerbosity (Verbosity Logger.Info)-                          (cmap renderPrettyWithSeverity (fromCologAction debugAdapterLogger))-      Debugger.runDebugger final_logger writeDebuggerOutput rootDir componentDir libdir units ghcInvocation extraGhcArgs mainFp runConf $ do+      Debugger.runDebugger l rootDir componentDir libdir units ghcInvocation extraGhcArgs mainFp runConf $ do         liftIO $ do           tid <- myThreadId           labelThread tid "Main Debugger Thread"         liftIO $ signalInitialized (Right ())         forever $ do           req <- takeMVar requests & liftIO-          resp <- (Debugger.execute (cmapWithSev DebuggerLog recorder) req <&> Right)+          resp <- (Debugger.execute req <&> Right)                     `catch` \(e :: SomeException) ->                         pure (Left (displayExceptionWithContext e))           either bad reply resp@@ -296,19 +254,114 @@       hPutStrLn stderr m       putMVar replies (Aborted ("Aborted debugger thread: " ++ m)) --- | Reads from the read end of the handle to which the debugger writes compiler messages.--- Writes the compiler messages to the client console-handleDebuggerOutput :: Handle-                     -> (DebugAdaptorCont () -> IO ())-                     -> IO ()-handleDebuggerOutput readDebuggerOutput withAdaptor = do+--------------------------------------------------------------------------------+-- * Logging+--------------------------------------------------------------------------------+{-+Note [Debugger, debuggee, and DAP logs]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Specification for the logger given to `Debugger`: -  -- Mask exceptions to avoid being killed between reading a line and outputting it.-  (forever $ mask_ $ do-    line <- T.hGetLine readDebuggerOutput-    withAdaptor $ Output.neutral line-    ) `catch` -- handles read EOF-        \(_e::SomeException) ->-          -- Cleanly exit when readDebuggerOutput is closed or thread is killed.-          return ()+1. All -v3 DebuggerLog and GHCLog messages go to the normal stdout/stderr (this shows up in+  the OUTPUT console in VSCode, without having to send special messages) +2. All -v1 DebuggerLog, GHCLog, and all LogDebuggeeOut and LogDebuggeeErr output+  goes to the DAP console (this is DEBUG CONSOLE in VSCode)++3. All LogDebuggeeOut and LogDebuggeeErr output are forwarded to the proxy if+  the proxy is enabled.+-}++-- See Note [Debugger, debuggee, and DAP logs]+createDebuggerLogger+  :: LogAction IO DAPLog+  -> Handle -- ^ Handle to write to DAP output+  -> (Bool, Chan BS.ByteString, Chan BS.ByteString) -- ^ Proxy channels, and whether is supported+  -> IO (LogAction IO Debugger.DebuggerLog)+createDebuggerLogger l writeDAPOutput (supportsRunInTerminal, syncProxyOut, syncProxyErr) = do+  dapLogger <- handleLogger writeDAPOutput+  return $+    -- (1) (all output is logged to normal logger)+    contramap DAPDebuggerLog l <>+    -- (2) and (3) (log relevant output to DAP handle)+      LogAction (\case+        Debugger.DebuggerLog sev msg+          | sev >= Info -> do+            dapLogger <& T.pack (show msg)+        Debugger.GHCLog logflags msg_class srcSpan msg ->+          defaultLogActionWithHandles writeDAPOutput writeDAPOutput logflags msg_class srcSpan msg+        Debugger.LogDebuggeeOut txt -> debuggeeOut dapLogger syncProxyOut txt+        Debugger.LogDebuggeeErr txt -> debuggeeOut dapLogger syncProxyErr txt+        _ -> pure () -- don't log other messages, already logged to (1)+        )+  where+    debuggeeOut l' proxyChan txt = do+      -- (2)+      l' <& txt+      -- (3)+      when supportsRunInTerminal $+        writeChan proxyChan $+          T.encodeUtf8 (txt <> "\n")++--------------------------------------------------------------------------------+-- * Capturing stdout, stderr, and writing to self stdin+--------------------------------------------------------------------------------++-- | Hijack the current process stdin and forward to it the messages from the given channel+stdinForwardThread :: Bool -> Chan BS.ByteString -> (DebugAdaptorCont () -> IO ()) -> IO ()+stdinForwardThread runInTerminal syncIn _withAdaptor = do+  tid <- myThreadId+  labelThread tid "Stdin Forward Thread"+  when runInTerminal $ do+    -- We need to hijack stdin to write to it++    -- 1. Create a new pipe from writeEnd->readEnd+    (readEnd, writeEnd) <- createPipe++    -- 2. Substitute the read-end of the pipe by stdin+    _ <- hDuplicateTo readEnd stdin+    hClose readEnd -- we'll never need to read from readEnd++    forever $ do+      i <- readChan syncIn+      -- 3. Write to write-end of the pipe+      BS.hPut writeEnd i >> hFlush writeEnd++-- | This thread captures stdout from the debuggee and sends it to the client.+-- NOTE, redirecting the stdout handle is a process-global operation. So this thread+-- will capture ANY stdout the debuggee emits. Therefore you should never directly+-- write to stdout, but always write to the appropiate handle.+stdoutCaptureThread :: Bool -> Chan BS.ByteString -> (DebugAdaptorCont () -> IO ()) -> IO ()+stdoutCaptureThread runInTerminal syncOut withAdaptor = do+  tid <- myThreadId+  labelThread tid "Stdin Capture Thread"+  withInterceptedStdout $ \_ interceptedStdout -> do+    forever $ do+      line <- liftIO $ T.hGetLine interceptedStdout+      when runInTerminal $+        writeChan syncOut $ T.encodeUtf8 (line <> T.pack "\n")++      -- Always output to Debug Console+      withAdaptor $ Output.stdout line++-- | Like 'stdoutCaptureThread' but for stderr+stderrCaptureThread :: Bool -> Chan BS.ByteString -> (DebugAdaptorCont () -> IO ()) -> IO ()+stderrCaptureThread runInTerminal syncErr withAdaptor = do+  tid <- myThreadId+  labelThread tid "Stderr Capture Thread"+  withInterceptedStderr $ \_ interceptedStderr -> do+    forever $ do+      line <- liftIO $ T.hGetLine interceptedStderr+      when runInTerminal $+        writeChan syncErr $ T.encodeUtf8 (line <> "\n")++      -- Always output to Debug Console+      catch+        (withAdaptor $ Output.stderr line)+        (\(_ :: SomeException) ->+          throwIO (FailedToWriteToAdaptor line))++newtype FailedToWriteToAdaptor = FailedToWriteToAdaptor T.Text+instance Show FailedToWriteToAdaptor where+  show (FailedToWriteToAdaptor t) = "Failed to write to debug adapter: " ++ T.unpack t+instance Exception FailedToWriteToAdaptor
hdb/Development/Debug/Adapter/Proxy.hs view
@@ -4,7 +4,6 @@ module Development.Debug.Adapter.Proxy   ( serverSideHdbProxy   , runInTerminalHdbProxy-  , ProxyLog(..)   ) where  import DAP@@ -27,16 +26,9 @@ import qualified Data.ByteString.Char8 as BS8 import qualified Data.HashMap.Strict as H -import GHC.Debugger.Logger+import Colog.Core import Development.Debug.Adapter -newtype ProxyLog = ProxyLog T.Text-  deriving newtype Pretty---- | Connect to a running @hdb proxy@ process on the given port--- connectToHdbProxy :: Recorder (WithVerbosity x) -> Int -> DebugAdaptor ()--- connectToHdbProxy = _- -- | Fork a new thread to run the server-side of the proxy. -- -- 1. To setup:@@ -47,7 +39,7 @@ -- 2. In a loop, -- 2.1 Read stdin from the socket and push it to a Chan -- 2.1 Read from a stdout Chan and write to the socket-serverSideHdbProxy :: Recorder (WithSeverity ProxyLog)+serverSideHdbProxy :: LogAction IO (WithSeverity T.Text)                    -> MVar ()                    -> DebugAdaptor () serverSideHdbProxy l client_conn_signal = do@@ -67,7 +59,7 @@     myThreadId >>= \tid -> labelThread tid "Debug/Adapter/Proxy: TCP Server"     runTCPServerWithSocket sock $ \scket -> do -      logWith l Info $ ProxyLog $ T.pack $ "Connected to client on port " ++ show port ++ "...!"+      infoMsg (T.pack $ "Connected to client on port " ++ show port ++ "...!")       putMVar client_conn_signal () -- signal ready (see #95)        -- -- Read stdout from chan and write to socket@@ -76,7 +68,7 @@         labelThread tid "Debug/Adapter/Proxy: Forward stdout"         forever $ do           bs <- readChan dbOut-          logWith l Debug $ ProxyLog $ T.pack $ "Writing to socket: " ++ BS8.unpack bs+          debugMsg (T.pack $ "Writing to socket: " ++ BS8.unpack bs)           NBS.sendAll scket bs        -- Read stderr from chan and write to socket@@ -85,7 +77,7 @@         labelThread tid "Debug/Adapter/Proxy: Forward stderr"         forever $ do           bs <- readChan dbErr-          logWith l Debug $ ProxyLog $ T.pack $ "Writing to socket (from stderr): " ++ BS8.unpack bs+          debugMsg (T.pack $ "Writing to socket (from stderr): " ++ BS8.unpack bs)           NBS.sendAll scket bs        -- Read stdin from socket and write to chan@@ -93,10 +85,10 @@             bs <- NBS.recv scket 4096             if BS8.null bs               then do-                logWith l Debug $ ProxyLog $ T.pack "Connection to client was closed."+                debugMsg (T.pack "Connection to client was closed.")                 close scket               else do-                logWith l Debug $ ProxyLog $ T.pack $ "Read from socket: " ++ BS8.unpack bs+                debugMsg (T.pack $ "Read from socket: " ++ BS8.unpack bs)                 writeChan dbIn bs >> loop        in ignoreIOException loop @@ -104,7 +96,9 @@    where     ignoreIOException a = catch a $ \(e::IOException) ->-      logWith l Info $ ProxyLog $ T.pack $ "Ignoring connection broken to proxy client: " ++ show e+      infoMsg (T.pack $ "Ignoring connection broken to proxy client: " ++ show e)+    debugMsg msg = l <& WithSeverity msg Debug+    infoMsg msg  = l <& WithSeverity msg Info  -- | The proxy code running on the terminal in which the @hdb proxy@ process is launched. --@@ -112,9 +106,9 @@ -- 1. Connecting to the given proxy-server port -- 2. Forwarding stdin to the port it is connected to -- 3. Read from the network the output and write it to stdout-runInTerminalHdbProxy :: Recorder (WithSeverity ProxyLog) -> Int -> IO ()+runInTerminalHdbProxy :: LogAction IO (WithSeverity T.Text) -> Int -> IO () runInTerminalHdbProxy l port = do-  logWith l Info $ ProxyLog $ T.pack $ "Running in terminal on port " ++ show port ++ "...!"+  l <& WithSeverity (T.pack $ "Running in terminal on port " ++ show port ++ "...!") Info   hSetBuffering stdin LineBuffering    dbg_inv <- lookupEnv "DEBUGGEE_INVOCATION"@@ -137,7 +131,7 @@         msg <- NBS.recv sock 4096         if BS8.null msg           then do-            logWith l Info $ ProxyLog $ T.pack "Exiting..."+            l <& WithSeverity (T.pack "Exiting...") Info             close sock             exitSuccess           else BS8.hPut stdout msg >> hFlush stdout
hdb/Development/Debug/Adapter/Stepping.hs view
@@ -10,22 +10,36 @@  commandContinue :: DebugAdaptor () commandContinue = do+  resetObjectReferences   DidContinue er <- sendInterleaved DoContinue $     sendContinueResponse (ContinueResponse True)   handleEvalResult False er  commandNext :: DebugAdaptor () commandNext = do+  resetObjectReferences   DidStep er <- sendInterleaved DoStepLocal sendNextResponse   handleEvalResult True er  commandStepIn :: DebugAdaptor () commandStepIn = do+  resetObjectReferences   DidStep er <- sendInterleaved DoSingleStep sendStepInResponse   handleEvalResult True er  commandStepOut :: DebugAdaptor () commandStepOut = do+  resetObjectReferences   DidStep er <- sendInterleaved DoStepOut sendStepOutResponse   handleEvalResult True er +--------------------------------------------------------------------------------++-- | See "Lifetime of Objects References" in DAP specification.+resetObjectReferences :: DebugAdaptor ()+resetObjectReferences = do+  updateDebugSession $ \s ->+    s { stackFrameMap = mempty+      , breakpointMap = mempty+      , variablesMap  = mempty+      }
hdb/Development/Debug/Adapter/Stopped.hs view
@@ -15,6 +15,7 @@ module Development.Debug.Adapter.Stopped where  import Control.Monad+import qualified Data.IntMap as IM import qualified Data.Text as T  import DAP@@ -47,28 +48,30 @@ commandStackTrace :: DebugAdaptor () commandStackTrace = do   StackTraceArguments{..} <- getArguments-  GotStacktrace stackFrames <- sendSync (GetStacktrace (RemoteThreadId stackTraceArgumentsThreadId))-  responseFrames <- forM (zip stackFrames [1..]) $ \(stackFrame, stackFrameIx) -> do-    source <- fileToSource stackFrame.sourceSpan.file-    return defaultStackFrame-      { stackFrameId = stackTraceArgumentsThreadId*1000000 + stackFrameIx-      , stackFrameName = T.pack stackFrame.name-      , stackFrameLine = stackFrame.sourceSpan.startLine-      , stackFrameColumn = stackFrame.sourceSpan.startCol-      , stackFrameEndLine = Just stackFrame.sourceSpan.endLine-      , stackFrameEndColumn = Just stackFrame.sourceSpan.endCol-      , stackFrameSource = Just source-      }+  let threadId = RemoteThreadId stackTraceArgumentsThreadId+  GotStacktrace stackFrames <- sendSync (GetStacktrace threadId)+  (responseFrames, newStackFrameMap) <- fmap (unzip . concat) $+    forM (zip stackFrames [0..]) $ \(stackFrame, stackFrameIx) -> do+      freshId <- getFreshId+      source <- fileToSource stackFrame.sourceSpan.file+      let responseFrame = defaultStackFrame+            { stackFrameId = freshId+            , stackFrameName = T.pack stackFrame.name+            , stackFrameLine = stackFrame.sourceSpan.startLine+            , stackFrameColumn = stackFrame.sourceSpan.startCol+            , stackFrameEndLine = Just stackFrame.sourceSpan.endLine+            , stackFrameEndColumn = Just stackFrame.sourceSpan.endCol+            , stackFrameSource = Just source+            }+      let newMapEntry = (freshId, StackFrameIx threadId stackFrameIx)+      return [(responseFrame, newMapEntry)]++  updateDebugSession (\s -> s { stackFrameMap = s.stackFrameMap <> IM.fromList newStackFrameMap })+   sendStackTraceResponse StackTraceResponse     { stackFrames = responseFrames-    , totalFrames = if null responseFrames then Nothing else Just (length responseFrames) }---- | Come up with a unique identifier for a stack frame (only valid while in--- this stopped environment, see "Lifetime of Objects References" in DAP--- overview).------ The unique identifier is based on the threadId and index of the stack frame for this thread's stack.--- mkUniqueStackId ::+    , totalFrames = if null responseFrames then Nothing else Just (length responseFrames)+    }  -------------------------------------------------------------------------------- -- * Scopes@@ -77,14 +80,24 @@ -- | Command to get scopes for current stopped point commandScopes :: DebugAdaptor () commandScopes = do-  ScopesArguments{scopesArgumentsFrameId=_IGNORED_BUT_NOW_WE_HAVE_TO_CARE} <- getArguments-  GotScopes scopes <- sendSync GetScopes-  sendScopesResponse . ScopesResponse =<<-    mapM scopeInfoToScope scopes+  ScopesArguments{..} <- getArguments+  let frameId = scopesArgumentsFrameId+  sfMap <- stackFrameMap <$> getDebugSession+  case IM.lookup frameId sfMap of+    Nothing -> do+      sendErrorResponse (ErrorMessage (T.pack $ "Could not find stack frame for id " ++ show frameId)) Nothing+    Just six@(StackFrameIx threadId frameIx) -> do+      GotScopes scopes <- sendSync (GetScopes threadId frameIx)+      sendScopesResponse . ScopesResponse =<<+        mapM (scopeInfoToScope six) scopes  -- | 'ScopeInfo' to 'Scope'-scopeInfoToScope :: ScopeInfo -> DebugAdaptor Scope-scopeInfoToScope ScopeInfo{..} = do+scopeInfoToScope :: StackFrameIx -> ScopeInfo -> DebugAdaptor Scope+scopeInfoToScope six ScopeInfo{..} = do++  -- Update vars map+  varId <- freshVarIx six (scopeToVarRef kind)+   source <- fileToSource sourceSpan.file   return defaultScope     { scopeName = case kind of@@ -101,7 +114,7 @@     , scopeColumn = Just sourceSpan.startCol     , scopeEndLine = Just sourceSpan.endLine     , scopeEndColumn = Just sourceSpan.endCol-    , scopeVariablesReference = fromEnum (scopeToVarRef kind)+    , scopeVariablesReference = varId     }  --------------------------------------------------------------------------------@@ -112,42 +125,42 @@ commandVariables :: DebugAdaptor () commandVariables = do   VariablesArguments{..} <- getArguments-  let vk = toEnum variablesArgumentsVariablesReference-  GotVariables vars <- sendSync (GetVariables vk)-  sendVariablesResponse $ VariablesResponse $-    map varInfoToVariables (either (:[]) id vars)-  case vars of-    -- If the reply indicates this was an "inspect lazy variable" request-    -- (because the requested variable was forced instead of returning an-    -- expansion), invalidate the parent variables.-    ---    -- The client side seems to handle rendering only the bits which changed-    -- out very well, while preserving the variable tree expansion.-    -- In any case, we might have to pessimistically redo all variable-    -- responses because any value may be changed by an updated thunk, not only-    -- the parent variables.-    Left _-      -> sendInvalidatedEvent defaultInvalidatedEvent-          { invalidatedEventAreas = [InvalidatedAreasVariables]-          , invalidatedEventStackFrameId = Just 0 -- we only support one stack frame for now (TODO).-          }-    _ -> return () +  vsMap <- variablesMap <$> getDebugSession+  case IM.lookup variablesArgumentsVariablesReference vsMap of+    Nothing -> sendErrorResponse (ErrorMessage (T.pack $ "Could not find variable reference " ++ show variablesArgumentsVariablesReference)) Nothing+    Just (VariablesIx six@(StackFrameIx threadId frameIx) varRef) -> do+      GotVariables vars <- sendSync (GetVariables threadId frameIx varRef)+      sendVariablesResponse . VariablesResponse =<<+        mapM (varInfoToVariables six) (variableResultToList vars)+      case vars of+        -- If the reply indicates this was an "inspect lazy variable" request+        -- (because the requested variable was forced instead of returning an+        -- expansion), invalidate the parent variables.+        --+        -- The client side seems to handle rendering only the bits which changed+        -- out very well, while preserving the variable tree expansion.+        -- In any case, we might have to pessimistically redo all variable+        -- responses because any value may be changed by an updated thunk, not only+        -- the parent variables.+        ForcedVariable _+          -> sendInvalidatedEvent defaultInvalidatedEvent+              { invalidatedEventAreas = [InvalidatedAreasVariables]+              , invalidatedEventStackFrameId = Just 0 -- TODO: REVERSE LOOKUP OF (StackFrameIx threadId frameIx)+              }+        VariableFields _ -> return ()+ -- | 'VarInfo' to 'Variable's.------ Note that if 'VarInfo' is a nested structure, only the top-most VarInfo is--- returned (with the according namedVariables and indexedVariables sizes).------ The @'varFields'@ are ignored. If they are meant to be returned, they should--- be matched against and returned explicitly (see @'getVariables'@).-varInfoToVariables :: VarInfo -> Variable-varInfoToVariables VarInfo{..} =-  defaultVariable+varInfoToVariables :: StackFrameIx -> VarInfo -> DebugAdaptor Variable+varInfoToVariables six VarInfo{..} = do+  varId <- freshVarIx six varRef++  return defaultVariable     { variableName = T.pack varName     , variableValue = T.pack varValue     , variableType = Just $ T.pack varType     , variableEvaluateName = Just $ T.pack varName-    , variableVariablesReference = fromEnum varRef+    , variableVariablesReference = varId     , variableNamedVariables = Nothing     , variableIndexedVariables = Nothing     , variablePresentationHint = Just defaultVariablePresentationHint@@ -155,3 +168,13 @@         }     } +--------------------------------------------------------------------------------+-- Variable ix references+--------------------------------------------------------------------------------++freshVarIx :: StackFrameIx -> VariableReference -> DebugAdaptor Int+freshVarIx _ NoVariables = pure 0 -- No variables means the reference should be 0, e.g. denoting the variable is not expandable+freshVarIx six vr = do+  varId <- getFreshId+  updateDebugSession (\s -> s { variablesMap = IM.insert varId (VariablesIx six vr) s.variablesMap })+  return varId
hdb/Development/Debug/Interactive.hs view
@@ -1,6 +1,7 @@-{-# LANGUAGE LambdaCase, ViewPatterns, RecordWildCards #-}+{-# LANGUAGE LambdaCase, ViewPatterns, RecordWildCards, OverloadedRecordDot #-} module Development.Debug.Interactive where +import Data.Functor.Contravariant import System.IO import System.Exit import System.Directory@@ -16,38 +17,45 @@  import Development.Debug.Session.Setup -import GHC.Debugger.Logger+import Colog.Core import GHC.Debugger.Interface.Messages import GHC.Debugger.Monad import GHC.Debugger+import Control.Monad+import Data.List (intercalate)+import qualified Data.Maybe as Maybe +data RunOptions = RunOptions+  { runEntryFile :: FilePath+  , runEntryPoint :: String+  , runEntryArgs :: [String]+  }++data RunContext = RunContext+  { runCurrentThread :: Maybe RemoteThreadId+  , runLastCommand :: Maybe Command+  }+ -- | Interactive debugging monad-type InteractiveDM a = InputT (RWST (FilePath{-entry file-},String{-entry point-}, [String]{-run args-}) ()-                                (Maybe Command{-last cmd-}) Debugger) a+type InteractiveDM a = InputT (RWST RunOptions () RunContext Debugger) a  data InteractiveLog-  = DebuggerLog DebuggerLog-  | DebuggerMonadLog DebuggerMonadLog-  | FlagsLog FlagsLog--instance Pretty InteractiveLog where-  pretty = \ case-    DebuggerLog msg -> pretty msg-    FlagsLog msg -> pretty msg-    DebuggerMonadLog msg -> pretty msg+  = IDebuggerLog DebuggerLog+  | ISessionSetupLog (WithSeverity SessionSetupLog)  -- | Run it-runIDM :: Recorder (WithSeverity InteractiveLog)+runIDM :: LogAction IO InteractiveLog        -> String   -- ^ entryPoint        -> FilePath -- ^ entryFile        -> [String] -- ^ entryArgs        -> [String] -- ^ extraGhcArgs+       -> RunDebuggerSettings        -> InteractiveDM a        -> IO a-runIDM logger entryPoint entryFile entryArgs extraGhcArgs act = do+runIDM logger entryPoint entryFile entryArgs extraGhcArgs runConf act = do   projectRoot <- getCurrentDirectory -  let hieBiosLogger = cmapWithSev FlagsLog logger+  let hieBiosLogger = contramap ISessionSetupLog logger   runExceptT (hieBiosSetup hieBiosLogger projectRoot entryFile) >>= \case     Left e               -> exitWithMsg e     Right (Left e)       -> exitWithMsg e@@ -55,18 +63,14 @@       | HieBiosFlags{..} <- flags                          -> do -      let defaultRunConf = RunDebuggerSettings-            { supportsANSIStyling = True -- todo: check!!-            , supportsANSIHyperlinks = False-            }-       let absEntryFile = normalise $ projectRoot </> entryFile-      let debugRec = cmapWithSev DebuggerMonadLog logger+      let debugRec = contramap IDebuggerLog logger -      runDebugger debugRec stdout rootDir componentDir libdir units ghcInvocation extraGhcArgs absEntryFile defaultRunConf $+      runDebugger debugRec rootDir componentDir libdir units ghcInvocation extraGhcArgs absEntryFile runConf $         fmap fst $           evalRWST (runInputT (setComplete noCompletion defaultSettings) act)-                   (entryFile, entryPoint, entryArgs) Nothing+                   (RunOptions { runEntryFile = entryFile, runEntryPoint = entryPoint, runEntryArgs = entryArgs })+                   (RunContext { runLastCommand = Nothing, runCurrentThread = Nothing } )   where     exitWithMsg txt = do       hPutStrLn stderr txt@@ -84,57 +88,146 @@   --         _ -> return []  -- | Run the interactive command-line debugger-debugInteractive :: Recorder (WithSeverity InteractiveLog) -> InteractiveDM ()-debugInteractive recorder = withInterrupt loop+debugInteractive :: InteractiveDM ()+debugInteractive = withInterrupt loop   where-    debugRec = cmapWithSev DebuggerLog recorder     loop = handleInterrupt loop $ do       minput <- getInputLine "(hdb) "       case minput of         Nothing -> outputStrLn "Exiting..." >> liftIO (exitWith ExitSuccess)         Just "" -> do-          lift get >>= \case+          lift (gets runLastCommand) >>= \case             Nothing -> return ()-            Just (cmd :: Command) -> do-              out <- lift . lift $ execute debugRec cmd -- repeat last command-              printResponse debugRec out+            Just cmd -> do+              out <- lift . lift $ execute cmd -- repeat last command+              printResponse out         Just input -> do           mcmd <- parseCmd input-          lift $ put mcmd+          lift $ modify (\ o -> o { runLastCommand = mcmd })           case mcmd of             Nothing -> return ()             Just cmd -> do-              out <- lift . lift $ execute debugRec cmd-              printResponse debugRec out+              out <- lift . lift $ execute cmd+              printResponse out       loop +showExceptionDetails :: RemoteThreadId -> InteractiveDM ()+showExceptionDetails tid = do+  infoResp <- lift . lift $ execute (GetExceptionInfo tid)+  case infoResp of+    GotExceptionInfo exc_info -> outputStrLn $ renderExceptionInfo exc_info+    _ -> pure ()+  stackResp <- lift . lift $ execute (GetStacktrace tid)+  case stackResp of+    GotStacktrace (frame:_) ->+      outputStrLn $+        "Exception location: " ++ renderSourceSpan (frame.sourceSpan)+    _ -> outputStrLn "Exception location: <unknown>"+ -------------------------------------------------------------------------------- -- Printing -------------------------------------------------------------------------------- -printResponse :: Recorder (WithSeverity DebuggerLog) -> Response -> InteractiveDM ()-printResponse recd = \case-  DidEval er -> outputStrLn $ show er+printResponse :: Response -> InteractiveDM ()+printResponse = \case+  DidEval er -> outputEvalResult er   DidSetBreakpoint bf       -> outputStrLn $ show bf   DidRemoveBreakpoint bf    -> outputStrLn $ show bf   DidGetBreakpoints mb_span -> outputStrLn $ show mb_span   DidClearBreakpoints -> outputStrLn "Cleared all breakpoints."-  DidContinue er -> outputStrLn $ show er-  DidStep er -> printEvalResult recd er-  DidExec er -> outputStrLn $ show er+  DidContinue er -> outputEvalResult er+  DidStep er -> printEvalResult er+  DidExec er -> outputEvalResult er   GotThreads threads -> outputStrLn $ show threads   GotStacktrace stackframes -> outputStrLn $ show stackframes   GotScopes scopeinfos -> outputStrLn $ show scopeinfos-  GotVariables vis -> outputStrLn $ show vis -- (Either VarInfo [VarInfo])+  GotVariables vis -> outputVariables vis+  GotExceptionInfo exc_info -> outputStrLn $ renderExceptionInfo exc_info   Aborted err_str -> outputStrLn ("Aborted: " ++ err_str)   Initialised -> pure ()+  where+    outputEvalResult er = do+      outputStrLn (showEvalResult er)+      maybeShowException er+      rememberThreadContext er -printEvalResult :: Recorder (WithSeverity DebuggerLog) -> EvalResult -> InteractiveDM ()-printEvalResult recd EvalStopped{breakId=_} = do-  out <- lift . lift $ execute recd GetScopes-  printResponse recd out-printEvalResult _ er = outputStrLn $ show er+    maybeShowException EvalStopped{breakId = Nothing, breakThread=tid} =+      showExceptionDetails tid+    maybeShowException _ = pure () +    rememberThreadContext er =+      case er of+        EvalCompleted{} -> lift $ modify' (\ ctx -> ctx { runCurrentThread = Nothing } )+        EvalException{} -> pure () -- TODO: why does this not have a thread associated?+        EvalStopped{breakThread} -> lift $ modify' (\ ctx -> ctx { runCurrentThread = Just breakThread } )+        EvalAbortedWith{} -> lift $ modify' (\ ctx -> ctx { runCurrentThread = Nothing } )++    outputVariables (ForcedVariable var) = outputVariables (VariableFields [var])+    outputVariables (VariableFields vars) = do+       ctx <- lift get+       case runCurrentThread ctx of+         Just threadId ->+          mapM_ (outputVarWithFields  threadId 0) vars+         Nothing -> error "no thread id"++    outputVarWithFields threadId frameIx var = do+      outputStrLn (showVarInfo var)+      fields <- fetchFields threadId frameIx var+      mapM_ (outputStrLn . ("  " ++) . showVarInfo) fields++    fetchFields _ _ VarInfo{varRef = NoVariables} = pure []+    fetchFields threadId frameIx VarInfo{varRef = ref@(SpecificVariable _), ..} = do+      resp <- lift . lift $ execute (GetVariables threadId frameIx ref)+      case resp of+        GotVariables res -> pure (variableResultToList res)+        Aborted err -> outputStrLn ("Failed to fetch fields for " ++ varName ++ ": " ++ err) >> pure []+        _ -> outputStrLn ("Unexpected response when fetching fields for " ++ varName) >> pure []+    fetchFields _ _ _ = pure []++printEvalResult :: EvalResult -> InteractiveDM ()+printEvalResult EvalStopped{..} = do+  out <- lift . lift $ execute (GetScopes breakThread 0)+  printResponse out+  when (breakId == Nothing) $+    showExceptionDetails breakThread+printEvalResult er = outputStrLn $ showEvalResult er++showEvalResult :: EvalResult -> String+showEvalResult (EvalCompleted{..}) = resultVal+showEvalResult (EvalException{..}) = resultVal+showEvalResult (EvalStopped{}) = "Stopped at breakpoint"+showEvalResult (EvalAbortedWith err) = "Aborted: " ++ err++showVarInfoResult :: VariableResult -> String+showVarInfoResult (ForcedVariable vi) = showVarInfo vi+showVarInfoResult (VariableFields vis) = unlines $ map showVarInfo vis++showVarInfo :: VarInfo -> String+showVarInfo VarInfo{..} = unwords [varName, ":", varType, "=", varValue]++renderSourceSpan :: SourceSpan -> String+renderSourceSpan SourceSpan{..} =+  file ++ ":" ++ show startLine ++ ":" ++ show startCol++renderExceptionInfo :: ExceptionInfo -> String+renderExceptionInfo = unlines . go 0+  where+    go depth exInfo =+      let indent = replicate (depth * 2) ' '+          typeLine = indent ++ "Exception: " ++ exceptionInfoTypeName exInfo+          messageLine = indent ++ "Message: " ++ exceptionInfoMessage exInfo+          ctxLine = case exceptionInfoContext exInfo of+            Nothing -> indent ++ "Call stack: <unavailable>"+            Just ctx -> indent ++ "Call stack:\n" ++ indentMultiline (depth + 1) ctx+          innerLines = case exceptionInfoInner exInfo of+            [] -> []+            xs -> (indent ++ "Inner exceptions:") : concatMap (go (depth + 1)) xs+      in typeLine : messageLine : ctxLine : innerLines++    indentMultiline depth txt =+      let pref = replicate (depth * 2) ' '+      in intercalate "\n" (map (pref ++) (lines txt))+ -------------------------------------------------------------------------------- -- Command parser --------------------------------------------------------------------------------@@ -181,16 +274,16 @@    <> metavar "N:INT"    <> help "Ignore first N:INT times this breakpoint is hit" )) -runParser :: FilePath -> String -> [String] -> Parser Command-runParser entryFile entryPoint entryArgs =+runParser :: RunOptions -> Parser Command+runParser opts =   -- --entry <name> with some args   -- (DebugExecution <$> parseEntry <*> parseSomeArgs)   -- --entry <name> without any args   -- <|> (DebugExecution <$> parseEntry <*> pure [])   -- just some args-  (DebugExecution (mkEntry entryPoint) entryFile <$> parseSomeArgs)+  (DebugExecution (mkEntry (runEntryPoint opts)) (runEntryFile opts) <$> parseSomeArgs)   -- just "run"-  <|> (pure $ DebugExecution (mkEntry entryPoint) entryFile entryArgs)+  <|> (pure $ DebugExecution (mkEntry (runEntryPoint opts)) (runEntryFile opts) (runEntryArgs opts))   where     _parseEntry =       fmap mkEntry $@@ -205,21 +298,18 @@         ( metavar "ARGS" <> help "Arguments to pass to the entry point. If empty, the arguments given at the debugger invocation are used." ) )     mkEntry entry       | entry == "main" = MainEntry Nothing-      | otherwise = FunctionEntry entryPoint+      | otherwise = FunctionEntry (runEntryPoint opts)  -- | Combined parser for 'Command'-cmdParser :: FilePath -> String -> [String] -> Parser Command-cmdParser entryFile entryPoint entryArgs = hsubparser-  ( Options.Applicative.command "break"-    ( info (SetBreakpoint <$> breakpointParser <*> hitCountBreakParser <*> conditionalBreakParser)-      ( progDesc "Set a breakpoint" ) )-  <>+cmdParser :: RunOptions -> RunContext -> Parser Command+cmdParser opts ctx = hsubparser+   (     Options.Applicative.command "delete"     ( info (DelBreakpoint <$> breakpointParser)       ( progDesc "Delete a breakpoint" ) )   <>     Options.Applicative.command "run"-    ( info (runParser entryFile entryPoint entryArgs)+    ( info (runParser opts)       ( progDesc "Run the debuggee" ) )   <>     Options.Applicative.command "next"@@ -246,21 +336,53 @@     Options.Applicative.command "exit"     ( info (pure TerminateProcess)       ( progDesc "Terminate and exit the debugger session" ) )+  <>+    Options.Applicative.command "threads"+    ( info (pure GetThreads)+      ( progDesc "Print all user threads" ) )+  <>+    Options.Applicative.command "backtrace"+    ( info (stackTraceParser ctx <**> helper)+      ( progDesc "Print stack trace" ) )+  <>+    Options.Applicative.command "variables"+    ( info (variablesParser ctx <**> helper)+      ( progDesc "Print local variables" ) )+  <> Options.Applicative.command "break"+    ( info (SetBreakpoint <$> breakpointParser <*> hitCountBreakParser <*> conditionalBreakParser)+      ( progDesc "Set a breakpoint" ) )   ) +stackTraceParser :: RunContext -> Parser Command+stackTraceParser ctx =+  GetStacktrace <$> threadIdParser ctx "Print backtrace of THREAD_ID. Defaults to current thread at breakpoint."++variablesParser :: RunContext -> Parser Command+variablesParser ctx =+  GetVariables+    <$> threadIdParser ctx "Show variables of THREAD_ID. Defaults to current thread at breakpoint."+    <*> pure 0+    <*> pure LocalVariables++threadIdParser :: RunContext -> String -> Parser RemoteThreadId+threadIdParser ctx helpMsg =+     RemoteThreadId <$> argument auto (metavar "THREAD_ID" <> help helpMsg)+ <|> Maybe.maybe (empty <**> abortOption (ErrorMsg "Not stopped at a Breakpoint") mempty) pure (runCurrentThread ctx)+ -- | Main parser info-cmdParserInfo :: FilePath -> String -> [String] -> ParserInfo Command-cmdParserInfo entryFile entryPoint entryArgs = info (cmdParser entryFile entryPoint entryArgs)+cmdParserInfo :: RunOptions -> RunContext -> ParserInfo Command+cmdParserInfo opts ctx = info (cmdParser opts ctx)   ( fullDesc )  -- | Parse command line arguments parseCmd :: String -> InteractiveDM (Maybe Command) parseCmd input = do-  (entryFile, entryPoint, entryArgs) <- lift ask+  opts <- lift ask+  ctx <- lift get   let     res = execParserPure      parserPrefs-     (cmdParserInfo entryFile entryPoint entryArgs)+     (cmdParserInfo opts ctx)      (words input)    in case res of     Success x ->
hdb/Development/Debug/Options.hs view
@@ -4,22 +4,25 @@ module Development.Debug.Options   ( HdbOptions(..) ) where -import GHC.Debugger.Logger+import Colog.Core (Severity)  -- | The options `hdb` is invoked in the command line with data HdbOptions-  -- | @server --port <port>@+  -- | @server [--internal-interpreter] --port <port>@   = HdbDAPServer     { port :: Int-    , verbosity :: Verbosity+    , verbosity :: Severity+    , internalInterpreter :: Bool     }-  -- | @cli [--entry-point=<entryPoint>] [--extra-ghc-args="<args>"] [<entryFile>] -- [<entryArgs>]@+  -- | @cli [--internal-interpreter] [--entry-point=<entryPoint>] [--extra-ghc-args="<args>"] [<entryFile>] -- [<entryArgs>]@   | HdbCLI     { entryPoint :: String     , entryFile :: FilePath     , entryArgs :: [String]     , extraGhcArgs :: [String]-    , verbosity :: Verbosity+    , verbosity :: Severity+    , internalInterpreter :: Bool+    , debuggeeStdin :: Maybe FilePath     }    -- | @proxy --port <port>@@@ -37,6 +40,26 @@   -- See #44 for the original ticket   | HdbProxy     { port :: Int-    , verbosity :: Verbosity+    , verbosity :: Severity     }++  -- | Launch the custom-for-the-debugger external interpreter for running the+  -- debuggee process.+  --+  -- Using an external interpreter will guarantee the debugger and debuggee run+  -- on separate processes. This comes with many benefits:+  --  * Debugger vs debuggee threads naturally separated+  --  * Debugger vs debuggee stdin/stdout/stderr naturally separated+  --  * No *** Ignoring breakpoint when debugging the debugger against another program+  --+  --  A custom server is necessary for the custom commands (starting with GHC+  --  9.16) and because the external interpreter in GHC 9.14 is not compiled+  --  with -threaded, which is a requirement for using thread/stack cloning+  --  messages in the external process.+  --+  --  See #169+  | HdbExternalInterpreter+      { writeFd :: Int+      , readFd  :: Int+      } 
hdb/Development/Debug/Options/Parser.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE MultilineStrings #-} -- | Options parser using optparse-applicative for the debugger options in -- 'Development.Debug.Options' module Development.Debug.Options.Parser@@ -11,7 +12,7 @@ import qualified Options.Applicative import qualified Paths_haskell_debugger as P -import GHC.Debugger.Logger+import Colog.Core import Development.Debug.Options  --------------------------------------------------------------------------------@@ -26,7 +27,8 @@      <> short 'p'      <> metavar "PORT"      <> help "DAP server port" )-  <*> verbosityParser (Verbosity Debug)+  <*> verbosityParser Debug+  <*> internalInterpreterParser  -- | Parser for 'HdbCLI' options cliParser :: Parser HdbOptions@@ -51,8 +53,30 @@      <> metavar "GHC_ARGS"      <> value []      <> help "Additional flags to pass to the ghc invocation that loads the program for debugging" )-  <*> verbosityParser (Verbosity Warning)+  <*> verbosityParser Warning+  <*> internalInterpreterParser+  <*> (optional $ strOption+      ( long "debuggee-stdin"+     <> metavar "FILE"+     <> help """+          Provide a file to be used as stdin for the debuggee. +          If `hdb` is connected to a terminal, the debuggee input can be+          given interactively, interleaved with the debugger input.++          However, if the debuggee reads from stdin and `hdb` is invoked+          reading from a file or pipe (e.g. `hdb Main.hs < instructions`), then+          the debuggee output must be given separately using this flag (and+          `instructions` should only contain debugger commands).++          Otherwise, the debuggee and debugger will race to read from the file+          (regardless of buffering), which typically results in an error like+          `<stdin>: hGetLine: end of file`++          This flag does not do anything when using --internal-interpreter+        """+      ))+ -- | Parser for 'HdbProxy' options proxyParser :: Parser HdbOptions proxyParser = HdbProxy@@ -61,8 +85,19 @@      <> short 'p'      <> metavar "PORT"      <> help "proxy port to which the debugger connects" )-  <*> verbosityParser (Verbosity Warning)+  <*> verbosityParser Warning +-- | Parser for @hdb external-interpreter <write-fd> <read-fd>@+-- See Note [Custom external interpreter]+extInterpParser :: Parser HdbOptions+extInterpParser = HdbExternalInterpreter+  <$> argument auto+    ( metavar "WRITE_FD"+   <> help "external interpreter write file descriptor" )+  <*> argument auto+    ( metavar "READ_FD"+   <> help "external interpreter read file descriptor" )+ -- | Combined parser for HdbOptions hdbOptionsParser :: Parser HdbOptions hdbOptionsParser = hsubparser@@ -75,8 +110,11 @@  <> Options.Applicative.command "proxy"     ( info proxyParser       ( progDesc "Internal mode used by the DAP server to proxy the stdin/stdout to the DAP client's terminal" ) )+ <> Options.Applicative.command "external-interpreter"+    ( info extInterpParser+      ( progDesc "Start the custom-for-debugger external interpreter" ) )   )-  <|> cliParser  -- Default to CLI mode if no subcommand+  <|> cliParser -- Default to CLI mode if no subcommand  -- | Parser for --version flag versioner :: Parser (a -> a)@@ -87,7 +125,7 @@ -- The default verbosity differs by mode (#86): -- - DAP server mode: DEBUG -- - CLI mode: WARNING-verbosityParser :: Verbosity -> Parser Verbosity+verbosityParser :: Severity -> Parser Severity verbosityParser vdef = option verb     ( long "verbosity"    <> short 'v'@@ -96,13 +134,24 @@    <> help "Logger verbosity in [0..3] interval, where 0 is silent and 3 is debug"     )   where-    verb = Verbosity <$> (verbNum =<< auto)+    verb = verbNum =<< auto     verbNum n = case n :: Int of       0 -> pure Error       1 -> pure Warning       2 -> pure Info       3 -> pure Debug       _ -> readerAbort (ErrorMsg "Verbosity must be a value in [0..3]")++-- | Parser for --internal-interpreter+--+-- Prefer running the debuggee on the debugger's internal interpreter rather+-- than using an external interpreter by default.+internalInterpreterParser :: Parser Bool+internalInterpreterParser =+  switch+    ( long "internal-interpreter"+   <> help "Prefer running the debuggee on the debugger's internal interpreter rather than on a separate (external-interpreter) process"+    )  -- | Main parser info hdbParserInfo :: ParserInfo HdbOptions
hdb/Development/Debug/Session/Setup.hs view
@@ -10,7 +10,7 @@   , hieBiosSetup    -- * Logging-  , FlagsLog(..)+  , SessionSetupLog(..)   ) where  import Control.Applicative ((<|>))@@ -29,7 +29,7 @@ import System.FilePath import System.IO.Error import Text.ParserCombinators.ReadP (readP_to_S)-import Prettyprinter+import Data.Functor.Contravariant  import qualified Data.Text as T @@ -42,18 +42,13 @@ import qualified Hie.Locate as Implicit import qualified Hie.Yaml as Implicit -import GHC.Debugger.Logger+import Colog.Core -data FlagsLog+data SessionSetupLog   = HieBiosLog HIE.Log   | LogCradle (HIE.Cradle Void)   | LogSetupMsg T.Text--instance Pretty FlagsLog where-  pretty = \ case-    HieBiosLog msg -> pretty msg-    LogCradle crdl -> "Determined Cradle:" <+> viaShow crdl-    LogSetupMsg txt -> pretty txt+  deriving Show  -- | Flags inferred by @hie-bios@ to invoke GHC data HieBiosFlags = HieBiosFlags@@ -69,49 +64,48 @@       }  -- | Prepare a GHC session using hie-bios from scratch-hieBiosSetup :: Recorder (WithSeverity FlagsLog)+hieBiosSetup :: LogAction IO (WithSeverity SessionSetupLog)              -> FilePath -- ^ project root              -> FilePath -- ^ entry file              -> ExceptT String IO (Either String HieBiosFlags) hieBiosSetup logger projectRoot entryFile = do -  logT "Figuring out the right flags to compile the project using hie-bios..."+  logInfo "Figuring out the right flags to compile the project using hie-bios..."   cradle <- hieBiosCradle logger projectRoot entryFile & ExceptT    -- GHC is found in PATH (by hie-bios as well).-  logT "Checking GHC version against debugger version..."-  _version <- hieBiosRuntimeGhcVersion logger cradle+  logInfo "Checking GHC version against debugger version..."+  _version <- hieBiosRuntimeGhcVersion cradle -  logT "Discovering session flags with hie-bios..."-  r <- hieBiosFlags logger cradle projectRoot entryFile     & liftIO+  logInfo "Discovering session flags with hie-bios..."+  r <- hieBiosFlags cradle projectRoot entryFile     & liftIO -  logT "Session setup with hie-bios was successful."+  logInfo "Session setup with hie-bios was successful."   return r    where-    logT = logWith logger Info . LogSetupMsg . T.pack+    logInfo m = liftLogIO logger <& WithSeverity (LogSetupMsg (T.pack m)) Info  -- | Try implicit-hie and the builtin search to come up with a @'HIE.Cradle'@-hieBiosCradle :: Recorder (WithSeverity FlagsLog) {-^ Logger -}-              -> FilePath {-^ Project root -}-              -> FilePath {-^ Entry file relative to root -}+hieBiosCradle :: LogAction IO (WithSeverity SessionSetupLog)+              -> FilePath -- ^ Project root+              -> FilePath -- ^ Entry file relative to root               -> IO (Either String (HIE.Cradle Void)) hieBiosCradle logger root relTarget = runExceptT $ do   let target = root </> relTarget   explicitCradle <- HIE.findCradle target & liftIO   cradle <- maybe (loadImplicitCradle hieBiosLogger target)                   (HIE.loadCradle hieBiosLogger) explicitCradle & liftIO-  logWith logger Info $ LogCradle cradle+  liftLogIO logger <& WithSeverity (LogCradle cradle) Info   pure cradle   where-    hieBiosLogger = toCologAction $ cmapWithSev HieBiosLog logger+    hieBiosLogger = contramap (fmap HieBiosLog) logger  -- | Fetch the runtime GHC version, according to hie-bios, and check it is the -- same as the compile time GHC version-hieBiosRuntimeGhcVersion :: Recorder (WithSeverity FlagsLog)-                         -> HIE.Cradle Void+hieBiosRuntimeGhcVersion :: HIE.Cradle Void                          -> ExceptT String IO Version-hieBiosRuntimeGhcVersion _logger cradle = do+hieBiosRuntimeGhcVersion cradle = do   out <- liftIO (HIE.getRuntimeGhcVersion cradle) >>= unwrapCradleResult "Failed to get runtime GHC version"    case versionMaybe out of@@ -129,12 +123,11 @@       pure actualVersion  -- | Make 'HieBiosFlags' from the given target file-hieBiosFlags :: Recorder (WithSeverity FlagsLog) {-^ Logger -}-             -> HIE.Cradle Void {-^ Project cradle the entry file belongs to -}+hieBiosFlags :: HIE.Cradle Void {-^ Project cradle the entry file belongs to -}              -> FilePath {-^ Project root -}              -> FilePath {-^ Entry file relative to root -}              -> IO (Either String HieBiosFlags)-hieBiosFlags _logger cradle root relTarget = runExceptT $ do+hieBiosFlags cradle root relTarget = runExceptT $ do   let target = root </> relTarget   libdir <- liftIO (HIE.getRuntimeGhcLibDir cradle) >>= unwrapCradleResult "Failed to get runtime GHC libdir" 
hdb/Main.hs view
@@ -1,6 +1,9 @@-{-# LANGUAGE OverloadedStrings, OverloadedRecordDot, CPP, DeriveAnyClass, DeriveGeneric, DerivingVia, LambdaCase, RecordWildCards, ViewPatterns #-}+{-# LANGUAGE OverloadedStrings, OverloadedRecordDot, CPP, DeriveAnyClass,+   DeriveGeneric, DerivingVia, LambdaCase, RecordWildCards, ViewPatterns,+   DataKinds #-} module Main where +import System.Process import System.Environment import Data.Maybe import Data.IORef@@ -9,6 +12,7 @@ import Control.Monad import Control.Monad.IO.Class import Control.Monad.Except+import Control.Exception (uninterruptibleMask) import Control.Exception.Backtrace  import DAP@@ -18,17 +22,27 @@ import Development.Debug.Adapter.Stepping import Development.Debug.Adapter.Stopped import Development.Debug.Adapter.Evaluation+import Development.Debug.Adapter.ExceptionInfo import Development.Debug.Adapter.Exit import Development.Debug.Adapter.Handles-import GHC.Debugger.Logger-import Prettyprinter+import Colog.Core -import System.IO (hSetBuffering, BufferMode(LineBuffering))+import Data.Time+import System.IO (hSetBuffering, BufferMode(LineBuffering), Handle, openFile, IOMode(ReadMode)) import qualified DAP.Log as DAP+import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import GHC.IO.Handle.FD+import Data.Functor.Contravariant +import qualified GHCi.Server as GHCi+import qualified GHCi.Signals as GHCi+import qualified GHCi.Utils as GHCi+import qualified GHCi.Message as GHCi++import GHC.Utils.Logger (defaultLogActionWithHandles)+import GHC.Debugger.Monad (DebuggerLog(..), RunDebuggerSettings(..)) import Development.Debug.Options (HdbOptions(..)) import Development.Debug.Options.Parser (parseHdbOptions) import Development.Debug.Adapter@@ -37,45 +51,78 @@  -------------------------------------------------------------------------------- -defaultStdoutForwardingAction :: T.Text -> IO ()-defaultStdoutForwardingAction l = do-  T.hPutStrLn stderr ("[INTERCEPTED STDOUT] " <> l)- main :: IO () main = do   setBacktraceMechanismState CostCentreBacktrace False   setBacktraceMechanismState HasCallStackBacktrace True-  setBacktraceMechanismState IPEBacktrace True+  setBacktraceMechanismState IPEBacktrace False -- TODO: True, see #181 -  hdbOpts <- parseHdbOptions-  let-    timeStampLogger  = cmapIO renderWithTimestamp . fromCologAction-    loggerWithSev    = cmap renderPrettyWithSeverity-    loggerFinal opts = applyVerbosity opts.verbosity . loggerWithSev . timeStampLogger+  allArgs <- getArgs+  hdbOpts <- case allArgs of+    [writeFd, readFd, "--external-interpreter"] ->+         -- Special case to detect --external-interpreter in the third+         -- position. If we could specify -opti options to put *before* the+         -- descriptors we could get rid of this.+         pure (HdbExternalInterpreter (read writeFd) (read readFd))+    _ -> parseHdbOptions   case hdbOpts of-    HdbDAPServer{port} -> do+    HdbDAPServer{port, internalInterpreter} -> do       config <- getConfig port-      withInterceptedStdoutForwarding defaultStdoutForwardingAction $ \realStdout -> do+      redirectRealStdout internalInterpreter $ \realStdout -> do         hSetBuffering realStdout LineBuffering-        l <- handleLogger realStdout-        let dapLogger = cmap DAP.renderDAPLog $ timeStampLogger l-        let runLogger = loggerFinal hdbOpts l+        l <- mainLogger hdbOpts.verbosity realStdout         init_var <- liftIO (newIORef False{-not supported by default-})         pid_var  <- liftIO (newIORef Nothing)         ccon_var <- liftIO newEmptyMVar-        runDAPServerWithLogger (toCologAction dapLogger) config-          (talk runLogger init_var pid_var ccon_var)-          (ack runLogger pid_var)+        runDAPServerWithLogger (contramap DAPLibraryLog l) config+          (talk l init_var pid_var ccon_var internalInterpreter)+          (ack l pid_var)     HdbCLI{..} -> do-        l <- handleLogger stdout-        let runLogger = cmapWithSev InteractiveLog $ loggerFinal hdbOpts l-        runIDM runLogger entryPoint entryFile entryArgs extraGhcArgs $-          debugInteractive runLogger+        l <- mainLogger hdbOpts.verbosity stdout+        stdinStream <- case debuggeeStdin of+          Just fp -> UseHandle <$> System.IO.openFile fp ReadMode+          Nothing -> pure Inherit+        let runConf = RunDebuggerSettings+              { supportsANSIStyling = True -- todo: check!!+              , supportsANSIHyperlinks = False+              , preferInternalInterpreter = internalInterpreter+              , externalInterpreterStdinStream = stdinStream+              }+        runIDM (contramap InteractiveLog l) entryPoint entryFile entryArgs extraGhcArgs+          runConf debugInteractive     HdbProxy{port} -> do-        l <- handleLogger stdout-        let runLogger = cmapWithSev RunProxyClientLog $ loggerFinal hdbOpts l-        runInTerminalHdbProxy runLogger port+        l <- mainLogger hdbOpts.verbosity stdout+        runInTerminalHdbProxy (contramap RunProxyClientLog l) port+    HdbExternalInterpreter{writeFd, readFd} -> do+      inh  <- GHCi.readGhcHandle (show readFd)+      outh <- GHCi.readGhcHandle (show writeFd)+      GHCi.installSignalHandlers+      pipe <- GHCi.mkPipeFromHandles inh outh+      let verbose = False+      uninterruptibleMask $ GHCi.serv verbose hook pipe+      where hook = return -- empty hook+        -- we cannot allow any async exceptions while communicating, because+        -- we will lose sync in the protocol, hence uninterruptibleMask.+  where+    -- When using the internal interpreter in DAP mode, we can't write to+    -- stdout directly because there will also be a thread forwarding the+    -- debuggee stdout by capturing it from stdout (and we'd get into a loop+    -- trying to forward what we're writing).+    --+    -- The redirection we use requires hDuplicateTo which isn't supported on+    -- Windows (ghc#22146), so using the internal interpreter on Windows+    -- currently unsupported.+    --+    -- When using the external interpreter, the debuggee output is read from+    -- its process handle directly, so this is unnecessary.+    redirectRealStdout internalInterpreter k+      | internalInterpreter =+        withInterceptedStdoutForwarding+          (\interceptedOut -> T.hPutStrLn stderr ("[INTERCEPTED STDOUT] " <> interceptedOut))+          (\realStdout -> k realStdout)+      | otherwise = k stdout + -- | Fetch config from environment, fallback to sane defaults getConfig :: Int -> IO ServerConfig getConfig port = do@@ -115,7 +162,7 @@       , supportsRestartRequest                = False       , supportsExceptionOptions              = True       , supportsValueFormattingOptions        = True-      , supportsExceptionInfoRequest          = False+      , supportsExceptionInfoRequest          = True       , supportTerminateDebuggee              = True       , supportSuspendDebuggee                = False       , supportsDelayedStackTraceLoading      = False@@ -148,25 +195,10 @@ -- * Talk -------------------------------------------------------------------------------- -data MainLog-  = InitLog InitLog-  | LaunchLog T.Text-  | InteractiveLog InteractiveLog-  | RunProxyServerLog ProxyLog-  | RunProxyClientLog ProxyLog--instance Pretty MainLog where-  pretty = \ case-    InitLog msg -> pretty msg-    LaunchLog msg -> pretty msg-    InteractiveLog msg -> pretty msg-    RunProxyServerLog msg -> pretty ("Proxy Server:" :: String) <+> pretty msg-    RunProxyClientLog msg -> pretty ("Proxy Client:" :: String) <+> pretty msg- -- | Main function where requests are received and Events + Responses are returned. -- The core logic of communicating between the client <-> adaptor <-> debugger -- is implemented in this function.-talk :: Recorder (WithSeverity MainLog)+talk :: LogAction IO MainLog      -> IORef Bool      -- ^ Whether the client supports runInTerminal      -> IORef (Maybe Int)@@ -174,9 +206,11 @@      -> MVar ()      -- ^ A var to block on waiting for the proxy client to connect, if a proxy      -- connection is expected. See #95.+     -> Bool+     -- ^ Prefer internal interpreter      -> Command -> DebugAdaptor () ---------------------------------------------------------------------------------talk l support_rit_var _pid_var client_proxy_signal = \ case+talk l support_rit_var _pid_var client_proxy_signal prefer_internal_interpreter = \ case   CommandInitialize -> do     InitializeRequestArguments{supportsRunInTerminalRequest} <- getArguments     let runInTerminal = fromMaybe False supportsRunInTerminalRequest@@ -186,14 +220,16 @@     -- If runInTerminal is not supported by the client, signal readiness right away     when (not runInTerminal) $       liftIO $ putMVar client_proxy_signal ()- --------------------------------------------------------------------------------   CommandLaunch -> do     launch_args <- getArguments      supportsRunInTerminalRequest <- liftIO $ readIORef support_rit_var -    merror <- runExceptT $ initDebugger (cmapWithSev InitLog l) supportsRunInTerminalRequest launch_args+    merror <- runExceptT $+      initDebugger (contramap DAPLog l)+        supportsRunInTerminalRequest prefer_internal_interpreter+        launch_args     case merror of       Right () -> do         sendLaunchResponse   -- ack@@ -204,9 +240,9 @@         when supportsRunInTerminalRequest $ do           -- Run proxy thread, server side, and           -- send the 'runInTerminal' request-          serverSideHdbProxy (cmapWithSev RunProxyServerLog l) client_proxy_signal+          serverSideHdbProxy (contramap RunProxyServerLog l) client_proxy_signal -        logWith l Info $ LaunchLog $ T.pack "Debugger launched successfully."+        liftLogIO l <& DAPLaunchLog (WithSeverity (T.pack "Debugger launched successfully.") Info)        Left (InitFailed err) -> do         sendErrorResponse (ErrorMessage (T.pack err)) Nothing@@ -220,6 +256,7 @@   CommandSetBreakpoints            -> commandSetBreakpoints   CommandSetFunctionBreakpoints    -> commandSetFunctionBreakpoints   CommandSetExceptionBreakpoints   -> commandSetExceptionBreakpoints+  CommandExceptionInfo             -> commandExceptionInfo   CommandSetDataBreakpoints        -> undefined   CommandSetInstructionBreakpoints -> undefined ----------------------------------------------------------------------------@@ -267,13 +304,82 @@ ----------------------------------------------------------------------------  -- | Receive reverse request responses (such as runInTerminal response)-ack :: Recorder (WithSeverity MainLog)+ack :: LogAction IO MainLog     -> IORef (Maybe Int)     -- ^ Reference to PID of runInTerminal proxy process running     -> ReverseRequestResponse -> DebugAdaptorCont () ack l _ref rrr = case rrr.reverseRequestCommand of   ReverseCommandRunInTerminal -> do     when rrr.success $ do-      logWith l Info $ LaunchLog $ T.pack "RunInTerminal was successful"+      liftLogIO l <& DAPLaunchLog (WithSeverity (T.pack "RunInTerminal was successful") Info)   _ -> pure () +--------------------------------------------------------------------------------+-- * Logging+--------------------------------------------------------------------------------++data MainLog+  = DAPLog DAPLog+  | InteractiveLog InteractiveLog+  | RunProxyServerLog (WithSeverity T.Text)+  | RunProxyClientLog (WithSeverity T.Text)+  | DAPLaunchLog (WithSeverity T.Text)+  | DAPLibraryLog DAP.DAPLog++-- | Given the severity threshold from which we start logging, create a base+-- logger for consuming the top-level debugger logs ('MainLog').+-- Outputs to given handle.+mainLogger :: Severity -> Handle -> IO (LogAction IO MainLog)+mainLogger threshold h = do+  l <- handleLogger h+  let+    logSessionLog (WithSeverity msg sev)+      | sev >= threshold =+        cmapM renderWithTimestamp l <& (renderSeverity sev <> T.pack (show msg))+      | otherwise = pure ()++    logDebuggerLog = \case+      DebuggerLog sev msg+        | sev >= threshold ->+          cmapM renderWithTimestamp l <&+            (renderSeverity sev <> T.pack (show msg))+      GHCLog logflags msg_class srcSpan msg ->+        defaultLogActionWithHandles h h logflags msg_class srcSpan msg+      LogDebuggeeOut out ->+        -- If we wanted, we could log the debuggee output differently if we are+        -- on the DAP debug mode vs, say, hdb.+        l <& out+      LogDebuggeeErr err -> l <& err+      _ -> pure ()++    defaultLog (WithSeverity msg sev)+      | sev >= threshold =+        cmapM renderWithTimestamp l <& (renderSeverity sev <> msg)+      | otherwise = pure ()++  pure $ LogAction $ \case+    DAPLog (DAPSessionSetupLog sessionLog)       -> logSessionLog sessionLog+    DAPLog (DAPDebuggerLog debuggerLog)          -> logDebuggerLog debuggerLog+    InteractiveLog (ISessionSetupLog sessionLog) -> logSessionLog sessionLog+    InteractiveLog (IDebuggerLog debuggerLog)    -> logDebuggerLog debuggerLog+    RunProxyServerLog sev_msg -> defaultLog sev_msg+    RunProxyClientLog sev_msg -> defaultLog sev_msg+    DAPLaunchLog sev_msg      -> defaultLog sev_msg+    DAPLibraryLog t ->+      l <& DAP.renderDAPLog t+  where+    renderSeverity :: Severity -> Text+    renderSeverity = \ case+      Debug -> "[DEBUG] "+      Info -> "[INFO] "+      Warning -> "[WARNING] "+      Error -> "[ERROR] "++    renderWithTimestamp :: Text -> IO Text+    renderWithTimestamp msg = do+      t <- getCurrentTime+      let timeStamp = utcTimeToText t+      pure $ "[" <> timeStamp <> "] " <> msg+      where+        utcTimeToText utcTime = T.pack $+          formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S%6QZ" utcTime
test/haskell/Main.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE LambdaCase, OverloadedStrings, ViewPatterns, QuasiQuotes, CPP #-} module Main (main) where +import Data.List (isSuffixOf)+import qualified Data.Set as Set import Text.RE.TDFA.Text.Lazy import Text.Printf import qualified Data.Text.Lazy as LT@@ -9,9 +11,11 @@ import qualified Data.ByteString.Lazy.Char8 as LBS import qualified System.Process as P import System.FilePath+import qualified System.FilePath.Posix as Posix import System.IO.Temp import System.Exit import System.IO+import System.Environment import Control.Exception  import Test.Tasty@@ -24,13 +28,33 @@  main :: IO () main = do-  goldens <- mapM (mkGoldenTest False) =<< findByExtension [".hdb-test"] "test/golden"+  env <- getEnvironment+  let mkTest = mkGoldenTest False env+  golden_tests_paths <- findByExtension [".hdb-test"] "test/golden"++  let internalOnlyTests = filter (\p -> ".internal" `isSuffixOf` takeBaseName p) golden_tests_paths+  let externalOnlyTests = filter (\p -> ".external" `isSuffixOf` takeBaseName p) golden_tests_paths++  let internalOnlySet = Set.fromList internalOnlyTests+  let externalOnlySet = Set.fromList externalOnlyTests+  let allTestsSet     = Set.fromList golden_tests_paths++  let defaultTestsSet = allTestsSet `Set.difference` (internalOnlySet `Set.union` externalOnlySet)++  let testsForInternal = Set.toList $ internalOnlySet `Set.union` defaultTestsSet+  let testsForExternal = Set.toList $ externalOnlySet `Set.union` defaultTestsSet++  default_goldens   <- mapM (mkTest "") testsForExternal+  intinterp_goldens <- mapM (mkTest "--internal-interpreter") testsForInternal+   defaultMain $+    testGroup "Tests"+      [ testGroup "Golden tests" default_goldens+      , #ifdef mingw32_HOST_OS-    ignoreTestBecause "Testsuite is not enabled on Windows (#149)" $+        ignoreTestBecause "Internal interpreter is not supported on Windows (#149 / ghc#22146)" $ #endif-    testGroup "Tests"-      [ testGroup "Golden tests" goldens+        testGroup "Golden tests (--internal-interpreter)" intinterp_goldens       , testGroup "Unit tests" unitTests       ] @@ -40,23 +64,28 @@   ]  -- | Receives as an argument the path to the @*.hdb-test@ which contains the--- shell invocation for running -mkGoldenTest :: Bool -> FilePath -> IO TestTree-mkGoldenTest keepTmpDirs path = do+-- shell invocation for running+mkGoldenTest :: Bool -> [(String, String)] -> FilePath -> String -> IO TestTree+mkGoldenTest keepTmpDirs inheritedEnv flags path = do   let testName   = takeBaseName     path   let goldenPath = replaceExtension path ".hdb-stdout"-  return (goldenVsStringComparing testName goldenPath action)+  return $ goldenVsStringComparing testName goldenPath action   where     action :: IO LBS.ByteString     action = do-      script <- readFile path       withHermeticDir keepTmpDirs (takeDirectory path) $ \test_dir -> do         (_, Just hout, _, p)-          <- P.createProcess (P.shell script){P.cwd = Just test_dir, P.std_out = P.CreatePipe}+          <- P.createProcess (P.proc "sh" [takeFileName path])+            { P.cwd = Just test_dir, P.std_out = P.CreatePipe+            , P.env = Just $+              inheritedEnv +++              [ ("HDB", "hdb " ++ flags)+              ]+            }         P.waitForProcess p >>= \case           ExitSuccess   -> LBS.hGetContents hout           ExitFailure c -> error $ "Test script in " ++ test_dir ++ " failed with exit code: " ++ show c-        + -------------------------------------------------------------------------------- -- Tasty Golden Advanced wrapper --------------------------------------------------------------------------------@@ -79,17 +108,44 @@   where   upd = createDirectoriesAndWriteFile ref . LT.encodeUtf8 +  escapePathSeparators c =+    if isPathSeparator c+      then "\\" ++ [c]+      else [c]++  useForwardSlashes =+    fmap useForwardSlash++  useForwardSlash c =+    if c == '\\'+      then '/'+      else c++  escapeRegex :: String -> String+  escapeRegex = concatMap escapePathSeparators+   -- Normalise the action producing the output   normalisingAct = do     tmpDir <- getCanonicalTemporaryDirectory+    let+      winTempDirWithForwardSlashes = useForwardSlashes tmpDir+    let posixTempDirRegex =+          escapeRegex $+            Posix.joinPath $+              [ winTempDirWithForwardSlashes+              , "[^/\\]+"+              , takeBaseName (takeDirectory ref)+              ]     replaceREs <- traverse (uncurry compileSearchReplace)-      [ ( tmpDir ++ ".*" ++ takeBaseName (takeDirectory ref) {- the folder in which the test is run, inside the canonical temp dir-}-        , "<TEMPORARY-DIRECTORY>" )-      , ( "Using cabal specification: .*"+      [ ( "Using cabal specification: .*"         , "Using cabal specification: <VERSION>" )-      ]+      , ( "\\\\\\\\", "/" ) -- Use forward slash+      , ( "\\\\", "/" )     -- Use forward slash+      , ( posixTempDirRegex {- the folder in which the test is run, inside the canonical temp dir-}+        , "<TEMPORARY-DIRECTORY>" )+     ] -    let normalising (LT.decodeUtf8 -> txt) = foldl' (*=~/) txt replaceREs+    let normalising (LT.decodeUtf8 -> txt) = LT.filter (/= '\r') $ foldl' (*=~/) txt replaceREs      normalising <$> act @@ -125,6 +181,6 @@             hFlush yH             hClose xH             hClose yH-            (_exitCode, out, err) <- P.readProcessWithExitCode "diff" ["-u", xf, yf] ""+            (_exitCode, out, err) <- P.readProcessWithExitCode "diff"+              ["-u", xf, yf] ""             return $ Just $ msg ++ "\nDiff output:\n" ++ out ++ err-
test/haskell/Test/DAP/RunInTerminal.hs view
@@ -32,13 +32,16 @@ #ifdef mingw32_HOST_OS       ignoreTestBecause "Needs to be fixed for Windows" $ #endif-      testCase "runInTerminal: proxy forwards stdin correctly" runInTerminal1+      testGroup "runInTerminal: proxy forwards stdin correctly"+        [ testCase "(default)" (runInTerminal1 "")+        , testCase "(--internal-interpreter)" (runInTerminal1 "--internal-interpreter")+        ]     ]  rit_keep_tmp_dirs :: Bool rit_keep_tmp_dirs = False -runInTerminal1 = do+runInTerminal1 flags = do   withHermeticDir rit_keep_tmp_dirs "test/unit/T44" $ \test_dir -> do      -- Come up with a random port@@ -46,7 +49,7 @@      -- Launch server process     (Just hin, Just hout, _, p)-      <- P.createProcess (P.shell $ "hdb server --port " ++ show testPort)+      <- P.createProcess (P.shell $ "hdb server " ++ flags ++ " --port " ++ show testPort)           {P.cwd = Just test_dir, P.std_out = P.CreatePipe, P.std_in = P.CreatePipe}      -- Fork thread to print out output of server process@@ -60,7 +63,8 @@               then return ()               else do                 _l <- hGetLine hout-                -- putStrLn ("[server] " ++ l)+                -- UNCOMMENT ME TO DEBUG+                -- putStrLn ("[server] " ++ _l)                 loop       loop