Skip to content

Commit 29c18be

Browse files
committed
Create a dummy stack frame and location for exceptions
We query the HasCallStack backtrace from an exception and if it's present, use that to show a location to the user.
1 parent c56de96 commit 29c18be

1 file changed

Lines changed: 119 additions & 31 deletions

File tree

haskell-debugger/GHC/Debugger/Stopped.hs

Lines changed: 119 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import GHC.Driver.Env as GHC
2121
import GHC.Runtime.Eval
2222
import GHC.Types.SrcLoc
2323
import GHC.InfoProv
24+
import GHC.Data.FastString (unpackFS)
2425
import GHC.Utils.Outputable as Ppr
2526
import qualified GHC.Unit.Home.Graph as HUG
2627

@@ -34,6 +35,10 @@ import GHC.Debugger.Interface.Messages
3435
import qualified GHC.Debugger.Interface.Messages as DbgStackFrame (DbgStackFrame(..))
3536
import GHC.Debugger.Utils
3637
import qualified GHC.Debugger.Logger as Logger
38+
import qualified GHC.Debugger.Runtime.Eval.RemoteExpr as Remote
39+
import GHC.Debugger.Runtime.Term.Parser
40+
import GHCi.RemoteTypes (castForeignRef)
41+
import GHC.Builtin.Types (anyTy)
3742

3843
{-
3944
Note [Don't crash if not stopped]
@@ -154,31 +159,36 @@ getStacktrace req_tid = do
154159
[] ->
155160
-- See Note [Don't crash if not stopped]
156161
return Nothing
157-
r:_
158-
| Just ss <- realSrcSpanToSourceSpan <$> srcSpanToRealSrcSpan (GHC.resumeSpan r)
159-
, Just ss /= (fmap DbgStackFrame.sourceSpan (listToMaybe decoded_frames))
160-
-- don't include the resume context entry if it is already at the
161-
-- start of the decoded frames
162-
, Just ibi <- GHC.resumeBreakpointId r
163-
-> do
164-
r_tid <- getRemoteThreadIdFromRemoteContext (GHC.resumeContext r)
165-
if r_tid == req_tid then do
166-
-- We're getting the stacktrace for the thread we're stopped at.
167-
info_brks <- liftIO $ readIModBreaks hug ibi
168-
let modl = getBreakSourceMod ibi info_brks
169-
modl_str <- display modl
170-
return $
171-
Just DbgStackFrame
172-
{ name = modl_str ++ "." ++ GHC.resumeDecl r
173-
, sourceSpan = ss
174-
, breakId = Just ibi
175-
}
176-
else
177-
return Nothing
178-
| otherwise ->
179-
-- No resume span; which should mean we're stopped on an exception.
180-
-- No info for now.
181-
return Nothing
162+
r:_ -> do
163+
let resumeSpanR = GHC.resumeSpan r
164+
mRealSpan = realSrcSpanToSourceSpan <$> srcSpanToRealSrcSpan resumeSpanR
165+
firstSpan = DbgStackFrame.sourceSpan <$> listToMaybe decoded_frames
166+
r_tid <- getRemoteThreadIdFromRemoteContext (GHC.resumeContext r)
167+
if r_tid /= req_tid then
168+
return Nothing
169+
else case GHC.resumeBreakpointId r of
170+
Just ibi
171+
| Just ss <- mRealSpan
172+
, Just ss /= firstSpan -> do
173+
-- We're getting the stacktrace for the thread we're stopped at.
174+
info_brks <- liftIO $ readIModBreaks hug ibi
175+
let modl = getBreakSourceMod ibi info_brks
176+
modl_str <- display modl
177+
return $
178+
Just DbgStackFrame
179+
{ name = modl_str ++ "." ++ GHC.resumeDecl r
180+
, sourceSpan = ss
181+
, breakId = Just ibi
182+
}
183+
_ -> do
184+
mExcSpan <- exceptionSourceSpanFromContext
185+
case mExcSpan of
186+
Just sourceSpan -> return $ Just DbgStackFrame
187+
{ name = GHC.resumeDecl r
188+
, sourceSpan
189+
, breakId = Nothing
190+
}
191+
Nothing -> return Nothing
182192
return (maybe id (:) head_frame $ decoded_frames)
183193

184194
--------------------------------------------------------------------------------
@@ -191,6 +201,12 @@ getScopes threadId frameIx = do
191201
frames <- getStacktrace threadId
192202
let frame = frames !! frameIx
193203
let sourceSpan = DbgStackFrame.sourceSpan frame
204+
localsScope = ScopeInfo
205+
{ kind = LocalVariablesScope
206+
, expensive = False
207+
, numVars = Nothing
208+
, sourceSpan
209+
}
194210
if
195211
| frameIx < length frames
196212
, Just ibi <- DbgStackFrame.breakId frame
@@ -204,11 +220,7 @@ getScopes threadId frameIx = do
204220
in_mod <- getTopEnv brk_modl
205221
imported <- getTopImported brk_modl
206222
return
207-
[ ScopeInfo { kind = LocalVariablesScope
208-
, expensive = False
209-
, numVars = Nothing
210-
, sourceSpan
211-
}
223+
[ localsScope
212224
, ScopeInfo { kind = ModuleVariablesScope
213225
, expensive = True
214226
, numVars = Just (sizeUFM in_mod)
@@ -221,7 +233,7 @@ getScopes threadId frameIx = do
221233
}
222234
]
223235
| otherwise ->
224-
return []
236+
return [localsScope]
225237

226238
--------------------------------------------------------------------------------
227239
-- * Variables
@@ -354,3 +366,79 @@ getTopImported modl = do
354366
liftIO $ HUG.lookupHugByModule modl (hsc_HUG hsc_env) >>= \case
355367
Nothing -> return emptyGlobalRdrEnv
356368
Just hmi -> mkTopLevImportedEnv hsc_env hmi
369+
370+
--------------------------------------------------------------------------------
371+
-- * Exception context helpers
372+
--------------------------------------------------------------------------------
373+
374+
exceptionSourceSpanFromContext :: Debugger (Maybe SourceSpan)
375+
exceptionSourceSpanFromContext = do
376+
GHC.getResumeContext >>= \case
377+
r:_ | resumeHistoryIx r == 0
378+
, Nothing <- GHC.resumeBreakpointId r -> do
379+
let excRef = resumeApStack r
380+
evalRes <- Remote.eval
381+
(Remote.raw exceptionLocationExpr `Remote.app` Remote.untypedRef excRef)
382+
case evalRes of
383+
Left err -> do
384+
logSDoc Logger.Debug $
385+
Ppr.text "Failed to evaluate exception context:" Ppr.<+> Ppr.text (show err)
386+
return Nothing
387+
Right fhv -> do
388+
parsed <- obtainParsedTerm "Exception context location" 4 True anyTy (castForeignRef fhv)
389+
(maybeParser exceptionLocationTupleParser)
390+
case parsed of
391+
Left errs -> do
392+
logSDoc Logger.Debug $
393+
Ppr.text "Failed to parse exception context location:"
394+
Ppr.<+> Ppr.vcat (map (Ppr.text . getTermErrorMessage) errs)
395+
return Nothing
396+
Right Nothing -> return Nothing
397+
Right (Just (file, line, col)) ->
398+
return $ Just SourceSpan
399+
{ file = file
400+
, startLine = line
401+
, startCol = col
402+
, endLine = line
403+
, endCol = col
404+
}
405+
_ -> return Nothing
406+
407+
exceptionLocationTupleParser :: TermParser (String, Int, Int)
408+
exceptionLocationTupleParser =
409+
(,,) <$> subtermWith 0 stringParser
410+
<*> subtermWith 1 intParser
411+
<*> subtermWith 2 intParser
412+
413+
exceptionLocationExpr :: String
414+
exceptionLocationExpr = unlines
415+
[ "let"
416+
, " fromCallStack cs = case GHC.Internal.Data.Maybe.listToMaybe (GHC.Internal.Stack.getCallStack cs) of"
417+
, " Just (_, loc) -> Just ( GHC.Internal.Stack.Types.srcLocFile loc"
418+
, " , GHC.Internal.Stack.Types.srcLocStartLine loc"
419+
, " , GHC.Internal.Stack.Types.srcLocStartCol loc)"
420+
, " go exc ="
421+
, " let ctx = GHC.Internal.Exception.Type.someExceptionContext exc"
422+
, " bts :: [GHC.Internal.Exception.Backtrace.Backtraces]"
423+
, " bts = Control.Exception.Context.getExceptionAnnotations ctx"
424+
, " in case bts of"
425+
, " bt : _ -> case GHC.Internal.Exception.Backtrace.btrHasCallStack bt of"
426+
, " Just cs -> fromCallStack cs"
427+
, " Nothing -> Nothing"
428+
, " [] -> Nothing"
429+
, " in go"
430+
]
431+
432+
fallbackExceptionSourceSpan :: Maybe SrcSpan -> SourceSpan
433+
fallbackExceptionSourceSpan mspan =
434+
let fileLabel = maybe "<exception>" spanLabel mspan
435+
in SourceSpan
436+
{ file = fileLabel
437+
, startLine = 0
438+
, startCol = 0
439+
, endLine = 0
440+
, endCol = 0
441+
}
442+
where
443+
spanLabel (RealSrcSpan rss _) = unpackFS (srcSpanFile rss)
444+
spanLabel (UnhelpfulSpan reason) = unpackFS (unhelpfulSpanFS reason)

0 commit comments

Comments
 (0)