Rodrigo Mesquita pushed to branch wip/romes/set-break-flags at Glasgow Haskell Compiler / GHC

Commits:

6 changed files:

Changes:

  • changelog.d/28080
    1
    +section: ghci
    
    2
    +synopsis: ``evalStmt <expr>`` break flags apply only to the main expression thread
    
    3
    +issues: #28080 #26064
    
    4
    +mrs: !16922
    
    5
    +
    
    6
    +description: {
    
    7
    +  The per-thread debugger flags (single-step, step-out and break-on-exception)
    
    8
    +  are now set only on the sandbox thread forked to evaluate a new expression,
    
    9
    +  rather than globally on all threads.
    
    10
    +
    
    11
    +  As a consequence, ``:step <expr>``, where ``<expr>`` forks a thread, now stops
    
    12
    +  at the next breakpoint in the thread evaluating ``<expr>``, not at a
    
    13
    +  breakpoint in any forked thread. A forked thread may still stop at a
    
    14
    +  breakpoint that was explicitly enabled, and ``:step``-ing from there enables
    
    15
    +  stepping for that thread.
    
    16
    +}

  • libraries/ghci/GHCi/Run.hs
    ... ... @@ -204,7 +204,8 @@ doSeq ref = do
    204 204
     resumeSeq :: RemoteRef (ResumeContext ()) -> IO (EvalStatus ())
    
    205 205
     resumeSeq hvref = do
    
    206 206
         ResumeContext{..} <- localRef hvref
    
    207
    -    withBreakAction evalOptsSeq resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
    
    207
    +    setBreakFlags evalOptsSeq resumeThreadId
    
    208
    +    withBreakAction resumeBreakMVar resumeStatusMVar $
    
    208 209
           mask_ $ do
    
    209 210
             putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    210 211
             redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
    
    ... ... @@ -233,15 +234,17 @@ sandboxIO opts io = do
    233 234
       -- We are running in uninterruptibleMask
    
    234 235
       breakMVar <- newEmptyMVar
    
    235 236
       statusMVar <- newEmptyMVar
    
    236
    -  withBreakAction opts breakMVar statusMVar Nothing $ do
    
    237
    +  withBreakAction breakMVar statusMVar $ do
    
    237 238
         let runIt = measureAlloc $ tryEval $ rethrow opts $ clearCCS io
    
    238 239
         if useSandboxThread opts
    
    239 240
            then do
    
    240 241
              tid <- forkIO $ do
    
    241 242
                tid <- myThreadId
    
    243
    +           setBreakFlags opts tid -- set flags on the sandbox thread
    
    242 244
                labelThread tid "GHCi sandbox"
    
    243 245
                unsafeUnmask runIt >>= putMVar statusMVar
    
    244 246
                                     -- empty: can't block
    
    247
    +
    
    245 248
              redirectInterrupts tid $ unsafeUnmask $ takeMVar statusMVar
    
    246 249
            else
    
    247 250
               -- GLUT on OS X needs to run on the main thread. If you
    
    ... ... @@ -328,34 +331,19 @@ tryEval io = do
    328 331
         Right a -> return (EvalSuccess a)
    
    329 332
     
    
    330 333
     -- This function sets up the interpreter for catching breakpoints, and
    
    331
    --- resets everything when the computation has stopped running.  This
    
    332
    --- is a not-very-good way to ensure that only the interactive
    
    334
    +-- resets the breakpoint action when the computation has stopped running.
    
    335
    +-- This is a not-very-good way to ensure that only the interactive
    
    333 336
     -- evaluation should generate breakpoints.
    
    334
    -withBreakAction :: EvalOpts -> MVar ()
    
    337
    +withBreakAction :: MVar ()
    
    335 338
                     -> MVar (EvalStatus b)
    
    336
    -                -> Maybe ThreadId -- ^ If resuming, the current threadId
    
    337 339
                     -> IO a -> IO a
    
    338
    -withBreakAction opts breakMVar statusMVar mtid act
    
    340
    +withBreakAction breakMVar statusMVar act
    
    339 341
      = bracket setBreakAction resetBreakAction (\_ -> act)
    
    340 342
      where
    
    341 343
        setBreakAction = do
    
    342 344
          stablePtr <- newStablePtr onBreak
    
    343 345
          poke breakPointIOAction stablePtr
    
    344
    -     when (breakOnException opts) $ do
    
    345
    -      case mtid of
    
    346
    -        Nothing -> rts_enableStopOnExceptionAll
    
    347
    -        Just (ThreadId tid) -> do
    
    348
    -          rts_enableStopOnException tid
    
    349
    -     when (singleStep opts) rts_enableStopNextBreakpointAll
    
    350
    -     when (stepOut opts) $ do
    
    351
    -      case mtid of
    
    352
    -        Nothing -> rts_enableStopNextBreakpointAll -- just enable single-step when no thread is stopped
    
    353
    -        Just (ThreadId tid) -> do
    
    354
    -          rts_enableStopAfterReturn tid
    
    355 346
          return stablePtr
    
    356
    -        -- Breaking on exceptions is not enabled by default, since it
    
    357
    -        -- might be a bit surprising.  The exception flag is turned off
    
    358
    -        -- as soon as it is hit, or in resetBreakAction below.
    
    359 347
     
    
    360 348
        onBreak :: BreakpointCallback
    
    361 349
        onBreak info_mod# info_mod_uid# infox# is_exception apStack = do
    
    ... ... @@ -387,21 +375,56 @@ withBreakAction opts breakMVar statusMVar mtid act
    387 375
     
    
    388 376
        resetBreakAction stablePtr = do
    
    389 377
          poke breakPointIOAction noBreakStablePtr
    
    390
    -     case mtid of
    
    391
    -      Just (ThreadId tid) -> rts_disableStopOnException tid
    
    392
    -      _                   -> rts_disableStopOnExceptionAll
    
    393
    -     rts_disableStopNextBreakpointAll
    
    394
    -     case mtid of
    
    395
    -      Just (ThreadId tid) -> rts_disableStopAfterReturn tid
    
    396
    -      _                   -> pure ()
    
    397 378
          freeStablePtr stablePtr
    
    398 379
     
    
    380
    +-- | Set the single-step, step-out, and break-on-exception flags on the target
    
    381
    +-- thread based on the given 'EvalOpts'.
    
    382
    +--
    
    383
    +-- Breaking on exceptions is not enabled by default, since it might be a
    
    384
    +-- bit surprising. The exception flag is turned off as soon as it is hit.
    
    385
    +--
    
    386
    +-- See Note [Always setBreakFlags on running thread]
    
    387
    +setBreakFlags :: EvalOpts -> ThreadId -> IO ()
    
    388
    +setBreakFlags opts (ThreadId tid#) = do
    
    389
    +  setFlag# breakOnException rts_enableStopOnException    rts_disableStopOnException
    
    390
    +  setFlag# singleStep       rts_enableStopNextBreakpoint rts_disableStopNextBreakpoint
    
    391
    +  setFlag# stepOut          rts_enableStopAfterReturn    rts_disableStopAfterReturn
    
    392
    +  where
    
    393
    +    setFlag# pred on off = if pred opts then on tid# else off tid#
    
    394
    +
    
    395
    +{-
    
    396
    +Note [Always setBreakFlags on running thread]
    
    397
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    398
    +Whenever we run a expression in the interpreter or unblock a paused thread we
    
    399
    +must make sure all breakpoint-flags for that thread are set according to the
    
    400
    +given options. There are three scenarios in which we set something running:
    
    401
    +
    
    402
    +- `evalStmt`, forks a thread to evaluate the given expression
    
    403
    +- `resumeStmt`, unblocks a given thread to resume execution
    
    404
    +- `abandonStmt`, kills a given thread to terminate execution (but the thread
    
    405
    +- will resume to handle the exception)
    
    406
    +
    
    407
    +In these three cases, if we musn't forget to set all breakpoint flags,
    
    408
    +since otherwise we'd resume execution with whatever stale state we had of the
    
    409
    +previous run command. A trivial example would be (tested by break037):
    
    410
    +
    
    411
    +1. `evalStmt` with { singleStep = True, breakOnError = True } would e.g. stop on
    
    412
    +  an exception and clear TSO_STOP_ON_EXCEPTION, but TSO_STOP_NEXT_BREAKPOINT
    
    413
    +  would still be active.
    
    414
    +2. `abandonStmt` if we didn't setBreakFlags again would kill the thread but
    
    415
    +  hit the next breakpoint bc TSO_STOP_NEXT_BREAKPOINT would still be active.
    
    416
    +
    
    417
    +Likewise for other interactions between eval/resume/abandon.
    
    418
    +TL;DR: always `setBreakFlags` when running/resuming a thread
    
    419
    +-}
    
    420
    +
    
    399 421
     resumeStmt
    
    400 422
       :: EvalOpts -> RemoteRef (ResumeContext [HValueRef])
    
    401 423
       -> IO (EvalStatus [HValueRef])
    
    402 424
     resumeStmt opts hvref = do
    
    403 425
       ResumeContext{..} <- localRef hvref
    
    404
    -  withBreakAction opts resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
    
    426
    +  setBreakFlags opts resumeThreadId
    
    427
    +  withBreakAction resumeBreakMVar resumeStatusMVar $
    
    405 428
         mask_ $ do
    
    406 429
           putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    407 430
           redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
    
    ... ... @@ -421,10 +444,20 @@ resumeStmt opts hvref = do
    421 444
     abandonStmt :: RemoteRef (ResumeContext [HValueRef]) -> IO ()
    
    422 445
     abandonStmt hvref = do
    
    423 446
       ResumeContext{..} <- localRef hvref
    
    447
    +  setBreakFlags evalOptsAbandon resumeThreadId
    
    424 448
       killThread resumeThreadId
    
    425 449
       putMVar resumeBreakMVar ()
    
    426 450
       _ <- takeMVar resumeStatusMVar
    
    427 451
       return ()
    
    452
    +  where
    
    453
    +    -- Don't stop anywhere on abandon
    
    454
    +    evalOptsAbandon = EvalOpts
    
    455
    +      { useSandboxThread = True
    
    456
    +      , singleStep = False
    
    457
    +      , stepOut    = False
    
    458
    +      , breakOnException = False
    
    459
    +      , breakOnError = False
    
    460
    +      }
    
    428 461
     
    
    429 462
     noBreakStablePtr :: StablePtr BreakpointCallback
    
    430 463
     noBreakStablePtr = unsafePerformIO $ newStablePtr noBreakAction
    

  • testsuite/tests/ghci.debugger/scripts/all.T
    ... ... @@ -157,3 +157,4 @@ test('T26042g', extra_files(['T26042g.hs']), ghci_script, ['T26042g.script'])
    157 157
     test('T27633c', extra_files(['T27633c_obj.hs']), ghci_script, ['T27633c.script'])
    
    158 158
     
    
    159 159
     test('RttiSharedTyVar', extra_files(['RttiSharedTyVar.hs']), ghci_script, ['RttiSharedTyVar.script'])
    
    160
    +test('break037', extra_files(['break037.hs']), ghci_script, ['break037.script'])

  • testsuite/tests/ghci.debugger/scripts/break037.hs
    1
    +module Main where
    
    2
    +
    
    3
    +import Control.Exception
    
    4
    +
    
    5
    +f :: () -> IO ()
    
    6
    +f () = throwIO (ErrorCall "boom") `finally` g ()
    
    7
    +
    
    8
    +g :: () -> IO ()
    
    9
    +g () = putStrLn "cleanup"
    
    10
    +
    
    11
    +main :: IO ()
    
    12
    +main = f ()

  • testsuite/tests/ghci.debugger/scripts/break037.script
    1
    +:l break037
    
    2
    +:set -fbreak-on-exception
    
    3
    +:step f ()
    
    4
    +:step
    
    5
    +-- Stepping into throwIO stops on the exception rather than at a breakpoint
    
    6
    +:step
    
    7
    +-- Running the cleanup in g while abandoning must not hit a breakpoint
    
    8
    +:abandon
    
    9
    +putStrLn "after abandon"

  • testsuite/tests/ghci.debugger/scripts/break037.stdout
    1
    +Stopped in Main.f, break037.hs:6:8-48
    
    2
    +_result :: IO () = _
    
    3
    +Stopped in Main.f, break037.hs:6:8-33
    
    4
    +_result :: IO () = _
    
    5
    +Stopped in <exception thrown>, <unknown>
    
    6
    +_exception :: e = _
    
    7
    +cleanup
    
    8
    +after abandon