Skip to content

Commit 1d66952

Browse files
Saizanalt-romes
authored andcommitted
[Fix #242] Re-import after resumeExec
1 parent 2935be7 commit 1d66952

6 files changed

Lines changed: 101 additions & 10 deletions

File tree

haskell-debugger/GHC/Debugger/Run.hs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,26 @@ import Data.Maybe
2121
import System.FilePath
2222
import System.Directory
2323

24-
import GHC
24+
import GHC qualified
25+
import GHC (
26+
ExecOptions (..),
27+
ExecResult (..),
28+
execStmt',
29+
ForeignHValue,
30+
GhciLStmt,
31+
GhcPs,
32+
InteractiveImport (..),
33+
mkHsString,
34+
ModSummary (..),
35+
Name,
36+
nlHsLit,
37+
nlList,
38+
parseImportDecl,
39+
SingleStep (..),
40+
SrcSpan (..),
41+
StmtLR (..),
42+
unLoc,
43+
)
2544
import GHC.Plugins (SourceError)
2645
import GHC.Builtin.Names (gHC_INTERNAL_GHCI_HELPERS)
2746
import GHC.Unit.Types
@@ -45,7 +64,7 @@ import GHC.Debugger.Interface.Messages
4564
import Colog.Core as Logger
4665
import qualified GHC.Debugger.Breakpoint.Map as BM
4766
import GHC.Debugger.Runtime.Thread
48-
import GHC.Debugger.Session (setInteractiveDebuggerDynFlags, getInteractiveDebuggerDynFlags)
67+
import GHC.Debugger.Session (setInteractiveDebuggerDynFlags, getInteractiveDebuggerDynFlags, resumeExec)
4968

5069
--------------------------------------------------------------------------------
5170
-- * Evaluation
@@ -126,27 +145,27 @@ debugExecution entryFile entry args = do
126145
-- | Resume execution of the stopped debuggee program
127146
doContinue :: Debugger EvalResult
128147
doContinue = do
129-
GHC.resumeExec RunToCompletion Nothing
148+
resumeExec RunToCompletion Nothing
130149
>>= handleExecResult
131150

132151
-- | Resume execution but only take a single step.
133152
doSingleStep :: Debugger EvalResult
134153
doSingleStep = do
135-
GHC.resumeExec SingleStep Nothing
154+
resumeExec SingleStep Nothing
136155
>>= handleExecResult
137156

138157
doStepOut :: Debugger EvalResult
139158
doStepOut = do
140159
mb_span <- getCurrentBreakSpan
141160
case mb_span of
142161
Nothing ->
143-
GHC.resumeExec (GHC.StepOut Nothing) Nothing
162+
resumeExec (GHC.StepOut Nothing) Nothing
144163
>>= handleExecResult
145164
Just loc -> do
146165
md <- fromMaybe (error "doStepOut") <$> getCurrentBreakModule
147166
ticks <- fromMaybe (error "doLocalStep:getTicks") <$> makeModuleLineMap md
148167
let current_toplevel_decl = enclosingTickSpan ticks loc
149-
GHC.resumeExec (GHC.StepOut (Just (RealSrcSpan current_toplevel_decl Strict.Nothing))) Nothing
168+
resumeExec (GHC.StepOut (Just (RealSrcSpan current_toplevel_decl Strict.Nothing))) Nothing
150169
>>= handleExecResult
151170

152171
-- | Resume execution but stop at the next tick within the same function.
@@ -162,13 +181,13 @@ doLocalStep = do
162181
Nothing -> error "not stopped at a breakpoint?!"
163182
Just (UnhelpfulSpan _) -> do
164183
liftIO $ putStrLn "Stopped at an exception. Forcing step into..."
165-
GHC.resumeExec SingleStep Nothing >>= handleExecResult
184+
resumeExec SingleStep Nothing >>= handleExecResult
166185
Just loc -> do
167186
md <- fromMaybe (error "doLocalStep") <$> getCurrentBreakModule
168187
-- TODO: Cache moduleLineMap?
169188
ticks <- fromMaybe (error "doLocalStep:getTicks") <$> makeModuleLineMap md
170189
let current_toplevel_decl = enclosingTickSpan ticks loc
171-
GHC.resumeExec (LocalStep (RealSrcSpan current_toplevel_decl mempty)) Nothing >>= handleExecResult
190+
resumeExec (LocalStep (RealSrcSpan current_toplevel_decl mempty)) Nothing >>= handleExecResult
172191

173192
-- | Generalized `doEval` that also handles `imports`
174193
doEvalCommand :: String -> Debugger EvalResult
@@ -226,7 +245,7 @@ doEval expr = withCurrentBreakExtensions $ do
226245
-- We use this in 'doEval' because we want to ignore breakpoints in expressions given at the prompt.
227246
continueToCompletion :: Debugger GHC.ExecResult
228247
continueToCompletion = do
229-
execr <- GHC.resumeExec GHC.RunToCompletion Nothing
248+
execr <- resumeExec GHC.RunToCompletion Nothing
230249
case execr of
231250
GHC.ExecBreak{} -> continueToCompletion
232251
GHC.ExecComplete{} -> return execr
@@ -324,7 +343,7 @@ handleExecResult = \case
324343
EvalAbortedWith e -> do
325344
logSDoc Logger.Warning (evalFailedMsg e)
326345
resume
327-
resume = GHC.resumeExec GHC.RunToCompletion Nothing >>= handleExecResult
346+
resume = resumeExec GHC.RunToCompletion Nothing >>= handleExecResult
328347

329348
-- | Get the value and type of a given 'Name' as rendered strings in 'VarInfo'.
330349
inspectName :: Name -> Debugger (Maybe VarInfo)

haskell-debugger/GHC/Debugger/Session.hs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module GHC.Debugger.Session (
2727
setPgmI, addOptI,
2828
setDynFlagWays,
2929
makeDynFlagsAbsoluteOverall,
30+
resumeExec,
3031
)
3132
where
3233

@@ -69,6 +70,7 @@ import qualified GHC.Unit.Home.Graph as HUG
6970
import qualified Data.Set as Set
7071
import Data.Maybe
7172
import GHC.Types.Target (InputFileBuffer)
73+
import GHC (SingleStep, ExecResult)
7274

7375
-- | Throws if package flags are unsatisfiable
7476
parseHomeUnitArguments :: GhcMonad m
@@ -482,6 +484,41 @@ addWay' w dflags0 =
482484
(wayUnsetGeneralFlags platform w)
483485
in dflags3
484486

487+
-- ----------------------------------------------------------------------------
488+
-- Wrappers around GHC's odd behavior
489+
-- ----------------------------------------------------------------------------
490+
491+
resumeExec :: GhcMonad m => SingleStep -> Maybe Int -> m ExecResult
492+
resumeExec a b = do
493+
-- IC's ic_imports field is not kept in sync with ic_gre_cache, so we could do
494+
-- this call later, but why rely on that.
495+
imports <- GHC.getContext
496+
497+
v <- GHC.resumeExec a b
498+
499+
-- To have interactive imports persist after a `continue` command we have to
500+
-- work around how GHC.resumeExec handles the InteractiveContext (IC).
501+
--
502+
-- GHC.resumeExec resets the scope of the IC (i.e. ic_gre_cache) to what it
503+
-- was before the last ExecBreak.
504+
--
505+
-- It makes sense for GHC.resumeExec to remove from the IC scope the
506+
-- breakpoint locals and anything that could have been defined with them, in
507+
-- fact they are also unloaded.
508+
--
509+
-- The way it's done though also rollbacks any import statements that were
510+
-- executed since the last ExecBreak. The only fix is to reimport everything
511+
-- again.
512+
--
513+
-- Note: GHC.setContext recomputes the scope of the interactive imports from
514+
-- scratch everytime. Considering `runDebugger` adds the whole home unit to
515+
-- the interactive imports this might become a bottleneck. GHC does not keep
516+
-- any reference to the scope containing just the imports, so we would have to
517+
-- cache it ourselves (and then extend it with the cached scope of
518+
-- ic_tythings, i.e. igre_prompt_env (c.f. replaceImportEnv)).
519+
GHC.setContext imports
520+
pure v
521+
485522
-- ----------------------------------------------------------------------------
486523
-- Utils that we need, but don't want to incur an additional dependency for.
487524
-- ----------------------------------------------------------------------------

test/golden/T242/Main.hs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
f () = do
2+
let xs = [3,2,1]
3+
pure ()
4+
print xs
5+
6+
main = do
7+
let doit = const (pure () :: IO ())
8+
let xs = [4,5,6]
9+
doit ()
10+
f ()
11+
print xs
12+
putStrLn "done"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[1 of 3] Compiling GHC.Debugger.View.Class ( in-memory:GHC.Debugger.View.Class, interpreted )[haskell-debugger-view-in-memory]
2+
[2 of 3] Compiling Main ( <TEMPORARY-DIRECTORY>/Main.hs, interpreted )[main]
3+
(hdb) BreakFound {changed = True, breakId = [InternalBreakpointId Main 4], sourceSpan = SourceSpan {file = "<TEMPORARY-DIRECTORY>/Main.hs", startLine = 9, endLine = 9, startCol = 3, endCol = 10}}
4+
(hdb) BreakFound {changed = True, breakId = [InternalBreakpointId Main 11], sourceSpan = SourceSpan {file = "<TEMPORARY-DIRECTORY>/Main.hs", startLine = 3, endLine = 3, startCol = 3, endCol = 10}}
5+
(hdb) Stopped at breakpoint
6+
(hdb) Aborted: <interactive>:1:8: error: [GHC-88464]
7+
Variable not in scope: sort :: [Integer] -> a0
8+
(hdb)
9+
(hdb) [4,5,6]
10+
()
11+
(hdb) Stopped at breakpoint
12+
(hdb) [1,2,3]
13+
()
14+
(hdb) Exiting...

test/golden/T242/T242.hdb-stdin

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
break Main.hs 9
2+
break Main.hs 3
3+
run
4+
print print (sort xs)
5+
print import Data.List
6+
print print (sort xs)
7+
continue
8+
print print (sort xs)

test/golden/T242/T242.hdb-test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$HDB Main.hs -v 0 < T242.hdb-stdin

0 commit comments

Comments
 (0)