Skip to content

Commit 4758f29

Browse files
fendorbgamari
authored andcommitted
Allow users to customise the collection of exception annotations
Add a global `CollectExceptionAnnotationMechanism` which determines how `ExceptionAnnotation`s are collected upon throwing an `Exception`. This API is exposed via `ghc-experimental`. By overriding how we collect `Backtraces`, we can control how the `Backtraces` are displayed to the user by newtyping `Backtraces` and giving a different instance for `ExceptionAnnotation`. A concrete use-case for this feature is allowing us to experiment with alternative stack decoders, without having to modify `base`, which take additional information from the stack frames. This commit does not modify how `Backtraces` are currently collected or displayed. (cherry picked from commit dee28cd)
1 parent 4665589 commit 4758f29

File tree

32 files changed

+239
-67
lines changed

32 files changed

+239
-67
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{-
2+
Module : GHC.Exception.Backtrace.Experimental
3+
Copyright : (c) The GHC Team
4+
License : see libraries/ghc-experimental/LICENSE
5+
6+
Maintainer : [email protected]
7+
Stability : experimental
8+
Portability : non-portable (GHC extensions)
9+
10+
This module exposes experimental extensions to the Backtrace mechanism of GHC.
11+
-}
12+
module GHC.Exception.Backtrace.Experimental (
13+
-- * Collecting exception annotations (like backtraces)
14+
CollectExceptionAnnotationMechanism,
15+
getCollectExceptionAnnotationMechanism,
16+
setCollectExceptionAnnotation,
17+
collectExceptionAnnotation,
18+
) where
19+
20+
import GHC.Internal.Exception.Backtrace

libraries/ghc-internal/src/GHC/Internal/Exception.hs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ import GHC.Internal.Show
7070
import GHC.Internal.Stack.Types
7171
import GHC.Internal.IO.Unsafe
7272
import {-# SOURCE #-} GHC.Internal.Stack (prettyCallStackLines, prettyCallStack, prettySrcLoc, withFrozenCallStack)
73-
import {-# SOURCE #-} GHC.Internal.Exception.Backtrace (collectBacktraces)
73+
import {-# SOURCE #-} GHC.Internal.Exception.Backtrace (collectExceptionAnnotation)
74+
import GHC.Internal.Exception.Context (SomeExceptionAnnotation(..))
7475
import GHC.Internal.Exception.Type
7576

7677
-- | Throw an exception. Exceptions may be thrown from purely
@@ -166,8 +167,8 @@ toExceptionWithBacktrace :: (HasCallStack, Exception e)
166167
=> e -> IO SomeException
167168
toExceptionWithBacktrace e
168169
| backtraceDesired e = do
169-
bt <- collectBacktraces
170-
return (addExceptionContext bt (toException e))
170+
SomeExceptionAnnotation ea <- collectExceptionAnnotation
171+
return (addExceptionContext ea (toException e))
171172
| otherwise = return (toException e)
172173

173174
-- | This is thrown when the user calls 'error'. The @String@ is the

libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import GHC.Internal.IO.Unsafe (unsafePerformIO)
1212
import GHC.Internal.Exception.Context
1313
import GHC.Internal.Ptr
1414
import GHC.Internal.Data.Maybe (fromMaybe)
15-
import GHC.Internal.Stack.Types as GHC.Stack (CallStack)
15+
import GHC.Internal.Stack.Types as GHC.Stack (CallStack, HasCallStack)
1616
import qualified GHC.Internal.Stack as HCS
1717
import qualified GHC.Internal.ExecutionStack.Internal as ExecStack
1818
import qualified GHC.Internal.Stack.CloneStack as CloneStack
@@ -86,6 +86,37 @@ setBacktraceMechanismState bm enabled = do
8686
_ <- atomicModifyIORef'_ enabledBacktraceMechanismsRef (setBacktraceMechanismEnabled bm enabled)
8787
return ()
8888

89+
-- | How to collect 'ExceptionAnnotation's on throwing 'Exception's.
90+
--
91+
data CollectExceptionAnnotationMechanism = CollectExceptionAnnotationMechanism
92+
{ ceaCollectExceptionAnnotationMechanism :: HasCallStack => IO SomeExceptionAnnotation
93+
}
94+
95+
defaultCollectExceptionAnnotationMechanism :: CollectExceptionAnnotationMechanism
96+
defaultCollectExceptionAnnotationMechanism = CollectExceptionAnnotationMechanism
97+
{ ceaCollectExceptionAnnotationMechanism = SomeExceptionAnnotation `fmap` collectBacktraces
98+
}
99+
100+
collectExceptionAnnotationMechanismRef :: IORef CollectExceptionAnnotationMechanism
101+
collectExceptionAnnotationMechanismRef =
102+
unsafePerformIO $ newIORef defaultCollectExceptionAnnotationMechanism
103+
{-# NOINLINE collectExceptionAnnotationMechanismRef #-}
104+
105+
-- | Returns the current callback for collecting 'ExceptionAnnotation's on throwing 'Exception's.
106+
--
107+
getCollectExceptionAnnotationMechanism :: IO CollectExceptionAnnotationMechanism
108+
getCollectExceptionAnnotationMechanism = readIORef collectExceptionAnnotationMechanismRef
109+
110+
-- | Set the callback for collecting an 'ExceptionAnnotation'.
111+
--
112+
setCollectExceptionAnnotation :: ExceptionAnnotation a => (HasCallStack => IO a) -> IO ()
113+
setCollectExceptionAnnotation collector = do
114+
let cea = CollectExceptionAnnotationMechanism
115+
{ ceaCollectExceptionAnnotationMechanism = fmap SomeExceptionAnnotation collector
116+
}
117+
_ <- atomicModifyIORef'_ collectExceptionAnnotationMechanismRef (const cea)
118+
return ()
119+
89120
-- | A collection of backtraces.
90121
data Backtraces =
91122
Backtraces {
@@ -124,6 +155,14 @@ displayBacktraces bts = concat
124155
instance ExceptionAnnotation Backtraces where
125156
displayExceptionAnnotation = displayBacktraces
126157

158+
-- | Collect 'SomeExceptionAnnotation' based on the configuration of the
159+
-- global 'CollectExceptionAnnotationMechanism'.
160+
--
161+
collectExceptionAnnotation :: HasCallStack => IO SomeExceptionAnnotation
162+
collectExceptionAnnotation = HCS.withFrozenCallStack $ do
163+
cea <- getCollectExceptionAnnotationMechanism
164+
ceaCollectExceptionAnnotationMechanism cea
165+
127166
-- | Collect a set of 'Backtraces'.
128167
collectBacktraces :: (?callStack :: CallStack) => IO Backtraces
129168
collectBacktraces = HCS.withFrozenCallStack $ do

libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs-boot

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ module GHC.Internal.Exception.Backtrace where
55

66
import GHC.Internal.Base (IO)
77
import GHC.Internal.Stack.Types (HasCallStack)
8-
import GHC.Internal.Exception.Context (ExceptionAnnotation)
9-
10-
data Backtraces
11-
12-
instance ExceptionAnnotation Backtraces
8+
import GHC.Internal.Exception.Context (SomeExceptionAnnotation)
139

1410
-- For GHC.Exception
15-
collectBacktraces :: HasCallStack => IO Backtraces
11+
collectExceptionAnnotation :: HasCallStack => IO SomeExceptionAnnotation

libraries/ghc-internal/tests/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This Makefile runs the tests using GHC's testsuite framework. It
2+
# assumes the package is part of a GHC build tree with the testsuite
3+
# installed in ../../../testsuite.
4+
5+
TOP=../../../testsuite
6+
include $(TOP)/mk/boilerplate.mk
7+
include $(TOP)/mk/test.mk

libraries/ghc-internal/tests/all.T

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This Makefile runs the tests using GHC's testsuite framework. It
2+
# assumes the package is part of a GHC build tree with the testsuite
3+
# installed in ../../../testsuite.
4+
5+
TOP=../../../../testsuite
6+
include $(TOP)/mk/boilerplate.mk
7+
include $(TOP)/mk/test.mk
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Control.Exception
2+
import Control.Exception.Annotation
3+
import GHC.Internal.Exception.Backtrace
4+
5+
data MyBacktraces = MyBacktraces
6+
7+
instance ExceptionAnnotation MyBacktraces where
8+
displayExceptionAnnotation MyBacktraces = "MyBacktraces"
9+
10+
main :: IO ()
11+
main = do
12+
setCollectExceptionAnnotation (pure MyBacktraces)
13+
catchAndPrint functionThatThrows
14+
catchAndPrint functionThatErrors
15+
16+
catchAndPrint :: IO () -> IO ()
17+
catchAndPrint act = do
18+
act `catch` \(exc :: SomeException) -> do
19+
putStrLn $ displayExceptionWithInfo exc
20+
21+
functionThatThrows :: IO ()
22+
functionThatThrows = do
23+
throwIO $ ErrorCall "functionThatThrows"
24+
25+
functionThatErrors :: a
26+
functionThatErrors = do
27+
error "functionThatErrors"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ghc-internal:GHC.Internal.Exception.ErrorCall:
2+
3+
functionThatThrows
4+
5+
MyBacktraces
6+
ghc-internal:GHC.Internal.Exception.ErrorCall:
7+
8+
functionThatErrors
9+
10+
MyBacktraces
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Control.Exception
2+
import Control.Exception.Annotation
3+
import GHC.Internal.Exception.Backtrace
4+
5+
6+
newtype MyBacktraces = MyBacktraces Backtraces
7+
8+
instance ExceptionAnnotation MyBacktraces where
9+
displayExceptionAnnotation (MyBacktraces bt) = unlines
10+
[ "My custom Backtraces:"
11+
, displayBacktraces bt
12+
]
13+
14+
main :: IO ()
15+
main = do
16+
setCollectExceptionAnnotation (MyBacktraces <$> collectBacktraces)
17+
catchAndPrint whileHandlingThrowIO
18+
catchAndPrint whileHandlingError
19+
20+
catchAndPrint :: IO () -> IO ()
21+
catchAndPrint act = do
22+
act `catch` \(exc :: SomeException) -> do
23+
putStrLn $ displayExceptionWithInfo exc
24+
25+
whileHandlingThrowIO :: IO ()
26+
whileHandlingThrowIO =
27+
handleJust
28+
(\e -> case e of
29+
ErrorCall{} -> Just e
30+
_ -> Nothing)
31+
(\_ -> throwIO $ ErrorCall "Error in Exception Handler")
32+
(throwIO $ ErrorCall "Main Error")
33+
34+
whileHandlingError :: IO ()
35+
whileHandlingError = do
36+
handleJust
37+
(\e -> case e of
38+
ErrorCall{} -> Just e
39+
_ -> Nothing)
40+
(\_ -> error "Error in Exception Handler")
41+
(error "Main Error")

0 commit comments

Comments
 (0)