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,23 @@ 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
    
    241
    +         wait <- newEmptyMVar
    
    240 242
              tid <- forkIO $ do
    
    243
    +
    
    244
    +           takeMVar wait -- don't run anything in this thread before
    
    245
    +                         -- `setBreakFlags` sets up.
    
    246
    +
    
    241 247
                tid <- myThreadId
    
    242 248
                labelThread tid "GHCi sandbox"
    
    243 249
                unsafeUnmask runIt >>= putMVar statusMVar
    
    244 250
                                     -- empty: can't block
    
    251
    +
    
    252
    +         setBreakFlags opts tid -- set flags on the sandbox thread
    
    253
    +         putMVar wait ()
    
    245 254
              redirectInterrupts tid $ unsafeUnmask $ takeMVar statusMVar
    
    246 255
            else
    
    247 256
               -- GLUT on OS X needs to run on the main thread. If you
    
    ... ... @@ -328,34 +337,19 @@ tryEval io = do
    328 337
         Right a -> return (EvalSuccess a)
    
    329 338
     
    
    330 339
     -- 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
    
    340
    +-- resets the breakpoint action when the computation has stopped running.
    
    341
    +-- This is a not-very-good way to ensure that only the interactive
    
    333 342
     -- evaluation should generate breakpoints.
    
    334
    -withBreakAction :: EvalOpts -> MVar ()
    
    343
    +withBreakAction :: MVar ()
    
    335 344
                     -> MVar (EvalStatus b)
    
    336
    -                -> Maybe ThreadId -- ^ If resuming, the current threadId
    
    337 345
                     -> IO a -> IO a
    
    338
    -withBreakAction opts breakMVar statusMVar mtid act
    
    346
    +withBreakAction breakMVar statusMVar act
    
    339 347
      = bracket setBreakAction resetBreakAction (\_ -> act)
    
    340 348
      where
    
    341 349
        setBreakAction = do
    
    342 350
          stablePtr <- newStablePtr onBreak
    
    343 351
          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 352
          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 353
     
    
    360 354
        onBreak :: BreakpointCallback
    
    361 355
        onBreak info_mod# info_mod_uid# infox# is_exception apStack = do
    
    ... ... @@ -387,21 +381,56 @@ withBreakAction opts breakMVar statusMVar mtid act
    387 381
     
    
    388 382
        resetBreakAction stablePtr = do
    
    389 383
          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 384
          freeStablePtr stablePtr
    
    398 385
     
    
    386
    +-- | Set the single-step, step-out, and break-on-exception flags on the target
    
    387
    +-- thread based on the given 'EvalOpts'.
    
    388
    +--
    
    389
    +-- Breaking on exceptions is not enabled by default, since it might be a
    
    390
    +-- bit surprising. The exception flag is turned off as soon as it is hit.
    
    391
    +--
    
    392
    +-- See Note [Always setBreakFlags on running thread]
    
    393
    +setBreakFlags :: EvalOpts -> ThreadId -> IO ()
    
    394
    +setBreakFlags opts (ThreadId tid#) = do
    
    395
    +  setFlag# breakOnException rts_enableStopOnException    rts_disableStopOnException
    
    396
    +  setFlag# singleStep       rts_enableStopNextBreakpoint rts_disableStopNextBreakpoint
    
    397
    +  setFlag# stepOut          rts_enableStopAfterReturn    rts_disableStopAfterReturn
    
    398
    +  where
    
    399
    +    setFlag# pred on off = if pred opts then on tid# else off tid#
    
    400
    +
    
    401
    +{-
    
    402
    +Note [Always setBreakFlags on running thread]
    
    403
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    404
    +Whenever we run a expression in the interpreter or unblock a paused thread we
    
    405
    +must make sure all breakpoint-flags for that thread are set according to the
    
    406
    +given options. There are three scenarios in which we set something running:
    
    407
    +
    
    408
    +- `evalStmt`, forks a thread to evaluate the given expression
    
    409
    +- `resumeStmt`, unblocks a given thread to resume execution
    
    410
    +- `abandonStmt`, kills a given thread to terminate execution (but the thread
    
    411
    +- will resume to handle the exception)
    
    412
    +
    
    413
    +In these three cases, if we musn't forget to set all breakpoint flags,
    
    414
    +since otherwise we'd resume execution with whatever stale state we had of the
    
    415
    +previous run command. A trivial example would be (tested by break037):
    
    416
    +
    
    417
    +1. `evalStmt` with { singleStep = True, breakOnError = True } would e.g. stop on
    
    418
    +  an exception and clear TSO_STOP_ON_EXCEPTION, but TSO_STOP_NEXT_BREAKPOINT
    
    419
    +  would still be active.
    
    420
    +2. `abandonStmt` if we didn't setBreakFlags again would kill the thread but
    
    421
    +  hit the next breakpoint bc TSO_STOP_NEXT_BREAKPOINT would still be active.
    
    422
    +
    
    423
    +Likewise for other interactions between eval/resume/abandon.
    
    424
    +TL;DR: always `setBreakFlags` when running/resuming a thread
    
    425
    +-}
    
    426
    +
    
    399 427
     resumeStmt
    
    400 428
       :: EvalOpts -> RemoteRef (ResumeContext [HValueRef])
    
    401 429
       -> IO (EvalStatus [HValueRef])
    
    402 430
     resumeStmt opts hvref = do
    
    403 431
       ResumeContext{..} <- localRef hvref
    
    404
    -  withBreakAction opts resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
    
    432
    +  setBreakFlags opts resumeThreadId
    
    433
    +  withBreakAction resumeBreakMVar resumeStatusMVar $
    
    405 434
         mask_ $ do
    
    406 435
           putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    407 436
           redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
    
    ... ... @@ -421,10 +450,20 @@ resumeStmt opts hvref = do
    421 450
     abandonStmt :: RemoteRef (ResumeContext [HValueRef]) -> IO ()
    
    422 451
     abandonStmt hvref = do
    
    423 452
       ResumeContext{..} <- localRef hvref
    
    453
    +  setBreakFlags evalOptsAbandon resumeThreadId
    
    424 454
       killThread resumeThreadId
    
    425 455
       putMVar resumeBreakMVar ()
    
    426 456
       _ <- takeMVar resumeStatusMVar
    
    427 457
       return ()
    
    458
    +  where
    
    459
    +    -- Don't stop anywhere on abandon
    
    460
    +    evalOptsAbandon = EvalOpts
    
    461
    +      { useSandboxThread = True
    
    462
    +      , singleStep = False
    
    463
    +      , stepOut    = False
    
    464
    +      , breakOnException = False
    
    465
    +      , breakOnError = False
    
    466
    +      }
    
    428 467
     
    
    429 468
     noBreakStablePtr :: StablePtr BreakpointCallback
    
    430 469
     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