Hannes Siebenhandl pushed to branch wip/fendor/freeze-throw at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • libraries/ghc-internal/src/GHC/Internal/Exception.hs
    ... ... @@ -87,7 +87,7 @@ throw e =
    87 87
         -- Note also the absolutely crucial `noinine` in the RHS!
    
    88 88
         --   See Note [Hiding precise exception signature in throw]
    
    89 89
         let se :: SomeException
    
    90
    -        !se = noinline (unsafePerformIO (toExceptionWithBacktrace e))
    
    90
    +        !se = noinline (unsafePerformIO (withFrozenCallStack $ toExceptionWithBacktrace e))
    
    91 91
         in raise# se
    
    92 92
     
    
    93 93
     -- Note [Capturing the backtrace in throw]
    
    ... ... @@ -162,7 +162,12 @@ throw e =
    162 162
     -- primops which allow more precise guidance of the demand analyser's heuristic
    
    163 163
     -- (e.g. #23847).
    
    164 164
     
    
    165
    --- | @since base-4.20.0.0
    
    165
    +-- | Collect a Backtrace and attach it to the 'Exception'.
    
    166
    +--
    
    167
    +-- It is recommended to use 'withFrozenCallStack' when calling this function
    
    168
    +-- in order to avoid leaking implementation details of 'toExceptionWithBacktrace'.
    
    169
    +--
    
    170
    +--  @since base-4.20.0.0
    
    166 171
     toExceptionWithBacktrace :: (HasCallStack, Exception e)
    
    167 172
                              => e -> IO SomeException
    
    168 173
     toExceptionWithBacktrace e
    

  • libraries/ghc-internal/src/GHC/Internal/STM.hs
    ... ... @@ -28,7 +28,7 @@ import GHC.Internal.Base
    28 28
     import GHC.Internal.Exception (Exception, toExceptionWithBacktrace, fromException, addExceptionContext)
    
    29 29
     import GHC.Internal.Exception.Context (ExceptionAnnotation)
    
    30 30
     import GHC.Internal.Exception.Type (WhileHandling(..))
    
    31
    -import GHC.Internal.Stack (HasCallStack)
    
    31
    +import GHC.Internal.Stack (HasCallStack, withFrozenCallStack)
    
    32 32
     
    
    33 33
     -- TVars are shared memory locations which support atomic memory
    
    34 34
     -- transactions.
    
    ... ... @@ -187,7 +187,7 @@ throwSTM e = do
    187 187
         -- N.B. Typically use of unsafeIOToSTM is very much frowned upon as this
    
    188 188
         -- is an easy way to end up with nested transactions. However, we can be
    
    189 189
         -- certain that toExceptionWithBacktrace will not initiate a transaction.
    
    190
    -    se <- unsafeIOToSTM (toExceptionWithBacktrace e)
    
    190
    +    se <- unsafeIOToSTM (withFrozenCallStack $ toExceptionWithBacktrace e)
    
    191 191
         STM $ raiseIO# se
    
    192 192
     
    
    193 193
     -- | Exception handling within STM actions.
    

  • libraries/ghc-internal/tests/backtraces/T15395.hs
    1
    +{-# LANGUAGE LambdaCase #-}
    
    2
    +{-# LANGUAGE TypeApplications #-}
    
    3
    +
    
    4
    +import GHC.Internal.Control.Exception
    
    5
    +import GHC.Internal.Data.Foldable (traverse_)
    
    6
    +import GHC.Internal.Exception.Backtrace
    
    7
    +import GHC.Internal.Exception.Context
    
    8
    +import GHC.Internal.Exception.Type
    
    9
    +import GHC.Internal.STM (atomically, throwSTM)
    
    10
    +import qualified GHC.Internal.Stack as HCS
    
    11
    +import qualified GHC.Internal.Stack.Types as HCS
    
    12
    +import Control.Monad (when)
    
    13
    +import qualified Data.List as List
    
    14
    +import System.Exit (exitFailure)
    
    15
    +
    
    16
    +main :: IO ()
    
    17
    +main = do
    
    18
    +  -- Make sure there are HasCallStackBacktraces
    
    19
    +  setBacktraceMechanismState HasCallStackBacktrace True
    
    20
    +  mapM_ (uncurry runCase)
    
    21
    +    [ ("throw", throwAction)
    
    22
    +    , ("throwIO", throwIOAction)
    
    23
    +    , ("error", errorAction)
    
    24
    +    , ("throwSTM", throwSTMAction)
    
    25
    +    , ("undefined", undefinedAction)
    
    26
    +    ]
    
    27
    +
    
    28
    +runCase :: String -> IO () -> IO ()
    
    29
    +runCase name act = do
    
    30
    +  putStrLn $ "=== Validate stack size of '" ++ name ++ "' has length 1"
    
    31
    +  catchAndVerifyStackTraceLength name act
    
    32
    +  putStrLn ""
    
    33
    +
    
    34
    +throwAction :: IO ()
    
    35
    +throwAction = evaluate $ throw $ ErrorCall "my throw error"
    
    36
    +
    
    37
    +throwIOAction :: IO ()
    
    38
    +throwIOAction = throwIO $ ErrorCall "my throwIO error"
    
    39
    +
    
    40
    +errorAction :: IO ()
    
    41
    +errorAction = error "plain error"
    
    42
    +
    
    43
    +throwSTMAction :: IO ()
    
    44
    +throwSTMAction = atomically $ throwSTM $ ErrorCall "my throwSTM error"
    
    45
    +
    
    46
    +undefinedAction :: IO ()
    
    47
    +undefinedAction = evaluate undefined
    
    48
    +
    
    49
    +catchAndVerifyStackTraceLength :: String -> IO () -> IO ()
    
    50
    +catchAndVerifyStackTraceLength name act = do
    
    51
    +  try act >>= \ case
    
    52
    +    Right _ -> do
    
    53
    +      putStrLn $ "Exception expected but got a result for '" ++ name ++ "'"
    
    54
    +      exitFailure
    
    55
    +    Left exc ->
    
    56
    +      verifyBacktraceSize name exc
    
    57
    +
    
    58
    +verifyBacktraceSize :: String -> SomeException -> IO ()
    
    59
    +verifyBacktraceSize label se = do
    
    60
    +  message <- evaluate (displayException se)
    
    61
    +  putStrLn "==== Caught exception:"
    
    62
    +  putStrLn message
    
    63
    +  putStrLn "==== Exception Backtraces:"
    
    64
    +  let backtraces = getExceptionAnnotations @Backtraces $ someExceptionContext se
    
    65
    +  traverse_ validateBacktrace backtraces
    
    66
    +
    
    67
    +validateBacktrace :: Backtraces -> IO ()
    
    68
    +validateBacktrace bt =
    
    69
    +  case btrHasCallStack bt of
    
    70
    +    Nothing -> pure ()
    
    71
    +    Just cs -> do
    
    72
    +      let stack = HCS.getCallStack cs
    
    73
    +
    
    74
    +      traverse_ mustNotReferenceInternalPackages stack
    
    75
    +      traverse_ (putStrLn . prettyCallSite) stack
    
    76
    +  where
    
    77
    +    prettyCallSite (f, loc) = "- " ++ f ++ ", called at " ++ HCS.prettySrcLoc loc
    
    78
    +
    
    79
    +    mustNotReferenceInternalPackages (_, loc) =
    
    80
    +      case List.find (HCS.srcLocPackage loc ==) internalPackages of
    
    81
    +        Just val -> fail $ "Stack trace must not reference '" ++ val ++ "' package."
    
    82
    +        Nothing -> pure ()
    
    83
    +
    
    84
    +internalPackages :: [String]
    
    85
    +internalPackages = ["base", "ghc", "ghc-internal", "ghc-experimental"]

  • libraries/ghc-internal/tests/backtraces/T15395.stdout
    1
    +=== Validate stack size of 'throw' has length 1
    
    2
    +==== Caught exception:
    
    3
    +my throw error
    
    4
    +==== Exception Backtraces:
    
    5
    +- throw, called at T15395.hs:35:26 in main:Main
    
    6
    +
    
    7
    +=== Validate stack size of 'throwIO' has length 1
    
    8
    +==== Caught exception:
    
    9
    +my throwIO error
    
    10
    +==== Exception Backtraces:
    
    11
    +- throwIO, called at T15395.hs:38:17 in main:Main
    
    12
    +
    
    13
    +=== Validate stack size of 'error' has length 1
    
    14
    +==== Caught exception:
    
    15
    +plain error
    
    16
    +==== Exception Backtraces:
    
    17
    +- error, called at T15395.hs:41:15 in main:Main
    
    18
    +
    
    19
    +=== Validate stack size of 'throwSTM' has length 1
    
    20
    +==== Caught exception:
    
    21
    +my throwSTM error
    
    22
    +==== Exception Backtraces:
    
    23
    +- throwSTM, called at T15395.hs:44:31 in main:Main
    
    24
    +
    
    25
    +=== Validate stack size of 'undefined' has length 1
    
    26
    +==== Caught exception:
    
    27
    +Prelude.undefined
    
    28
    +==== Exception Backtraces:
    
    29
    +- undefined, called at T15395.hs:47:28 in main:Main
    
    30
    +

  • libraries/ghc-internal/tests/backtraces/all.T
    ... ... @@ -4,3 +4,7 @@ test('T26507', [ when(have_profiling(), extra_ways(['prof']))
    4 4
                    , when(js_arch(), skip)
    
    5 5
                    , when(ghc_with_ipe(), skip) # IPE builds include an IPE backtrace section on stderr.
    
    6 6
                    , exit_code(1)], compile_and_run, [''])
    
    7
    +
    
    8
    +# Stack traces shouldn't expose implementation details
    
    9
    +test('T15395', [ when(have_profiling(), extra_ways(['prof']))
    
    10
    +               , when(js_arch(), skip)], compile_and_run, [''])