summaryrefslogtreecommitdiff
path: root/libraries/base/GHC/Exception/Backtrace.hs
blob: 8f955fa8d23af538c93e93fbf460a818b2412a9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_HADDOCK not-home #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  GHC.Exception.Backtrace
-- Copyright   :  (c) The GHC Team, 2020-2021
-- Authors     :  Ben Gamari, David Eichmann, Sven Tennie
-- License     :  see libraries/base/LICENSE
--
-- Maintainer  :  cvs-ghc@haskell.org
-- Stability   :  internal
-- Portability :  non-portable (GHC extensions)
--
-- Collect Exception backtraces with several mechanisms.
-----------------------------------------------------------------------------

module GHC.Exception.Backtrace
  ( Backtrace (..),
    BacktraceMechanism (..),
    setDefaultBacktraceMechanisms,
    getDefaultBacktraceMechanisms,
    showBacktraces,
    collectBacktraces,
    collectCostCenterBacktrace,
    collectExecutionStackBacktrace,
    collectIPEBacktrace,
    collectHasCallStackBacktrace
  )
where

import qualified Data.OldList as List
import Data.Maybe
import GHC.Base
import {-# SOURCE #-} GHC.ExecutionStack (Location, getStackTrace)
import {-# SOURCE #-} GHC.ExecutionStack.Internal (showStackFrames)
import GHC.IO.Unsafe
import {-# SOURCE #-} GHC.IORef
import GHC.Ptr
import GHC.Show
import {-# SOURCE #-} GHC.Stack
import {-# SOURCE #-} GHC.Stack.CCS
import GHC.Stack.CloneStack.Types (StackEntry)
import {-# SOURCE #-} GHC.Stack.CloneStack (cloneMyStack, decode)

-- | An exception backtrace.
--
-- @since 4.15
data Backtrace
  = -- | a cost center profiler backtrace
    CostCenterBacktrace (Ptr CostCentreStack)
  | -- | a stack from 'GHC.Stack.HasCallStack'
    HasCallStackBacktrace GHC.Stack.CallStack
  | -- | a stack unwinding (e.g. DWARF) backtrace
    ExecutionBacktrace [GHC.ExecutionStack.Location]
  | -- | a backtrace from Info Table Provenance Entries
    IPEBacktrace [StackEntry]

-- | @since 4.15
instance Show Backtrace where
  -- TODO
  showsPrec p (CostCenterBacktrace ccs) = showsPrec p ccs
  showsPrec p (HasCallStackBacktrace ccs) = showsPrec p ccs
  showsPrec _ (ExecutionBacktrace ccs) = showStackFrames ccs
  showsPrec p (IPEBacktrace stackEntries) = showsPrec p stackEntries

-- | Which kind of backtrace to collect when an exception is thrown.
data BacktraceMechanism
  = -- | collect a cost center stacktrace (only available when built with profiling)
    CostCenterBacktraceMech
  | -- | use execution stack unwinding with given limit
    ExecutionStackBacktraceMech
  | -- | collect backtraces from Info Table Provenance Entries
    IPEBacktraceMech
  | -- | use 'HasCallStack'
   HasCallStackBacktraceMech
  deriving (Eq, Show)

showBacktraces :: [Backtrace] -> String
showBacktraces bts = List.unlines $ List.intersperse "" $ map show bts

currentBacktraceMechanisms :: IORef [BacktraceMechanism]
currentBacktraceMechanisms = unsafePerformIO $ newIORef []
{-# NOINLINE currentBacktraceMechanisms #-}

-- | Set how 'Control.Exception.throwIO', et al. collect backtraces.
setDefaultBacktraceMechanisms :: [BacktraceMechanism] -> IO ()
setDefaultBacktraceMechanisms = writeIORef currentBacktraceMechanisms

-- | Returns the currently selected 'BacktraceMechanism'.
getDefaultBacktraceMechanisms :: IO [BacktraceMechanism]
getDefaultBacktraceMechanisms = readIORef currentBacktraceMechanisms

-- | Collect a list of 'Backtrace's via all current default 'BacktraceMechanism's.
-- See 'setDefaultBacktraceMechanisms'
collectBacktraces :: HasCallStack => IO [Backtrace]
collectBacktraces = do
    mechs <- getDefaultBacktraceMechanisms
    catMaybes `fmap` mapM collectBacktraces' mechs
  where
    -- | Collect a 'Backtrace' via the given 'BacktraceMechanism'.
    collectBacktraces' :: HasCallStack => BacktraceMechanism -> IO (Maybe Backtrace)
    collectBacktraces' CostCenterBacktraceMech = collectCostCenterBacktrace
    collectBacktraces' ExecutionStackBacktraceMech = collectExecutionStackBacktrace
    collectBacktraces' IPEBacktraceMech = collectIPEBacktrace
    collectBacktraces' HasCallStackBacktraceMech = collectHasCallStackBacktrace

collectCostCenterBacktrace :: IO (Maybe Backtrace)
collectCostCenterBacktrace = do
  ptr <- getCurrentCCS ()
  if ptr == nullPtr then
    pure Nothing
  else do
    pure $ Just (CostCenterBacktrace ptr)

collectExecutionStackBacktrace :: IO (Maybe Backtrace)
collectExecutionStackBacktrace = fmap ExecutionBacktrace `fmap` getStackTrace

collectIPEBacktrace :: IO (Maybe Backtrace)
collectIPEBacktrace = do
  stack <- cloneMyStack
  stackEntries <- decode stack
  pure $ Just $ IPEBacktrace stackEntries

collectHasCallStackBacktrace :: HasCallStack => IO (Maybe Backtrace)
collectHasCallStackBacktrace = pure . Just $ HasCallStackBacktrace callStack