| 1 | ----------------------------------------------------------------------------- |
|---|
| 2 | -- |
|---|
| 3 | -- GHCi Interactive debugging commands |
|---|
| 4 | -- |
|---|
| 5 | -- Pepe Iborra (supported by Google SoC) 2006 |
|---|
| 6 | -- |
|---|
| 7 | -- ToDo: lots of violation of layering here. This module should |
|---|
| 8 | -- decide whether it is above the GHC API (import GHC and nothing |
|---|
| 9 | -- else) or below it. |
|---|
| 10 | -- |
|---|
| 11 | ----------------------------------------------------------------------------- |
|---|
| 12 | |
|---|
| 13 | module Debugger (pprintClosureCommand, showTerm, pprTypeAndContents) where |
|---|
| 14 | |
|---|
| 15 | import Linker |
|---|
| 16 | import RtClosureInspect |
|---|
| 17 | |
|---|
| 18 | import GhcMonad |
|---|
| 19 | import HscTypes |
|---|
| 20 | import Id |
|---|
| 21 | import Name |
|---|
| 22 | import Var hiding ( varName ) |
|---|
| 23 | import VarSet |
|---|
| 24 | import UniqSupply |
|---|
| 25 | import TcType |
|---|
| 26 | import GHC |
|---|
| 27 | import Outputable |
|---|
| 28 | import PprTyThing |
|---|
| 29 | import MonadUtils |
|---|
| 30 | import Exception |
|---|
| 31 | |
|---|
| 32 | import Control.Monad |
|---|
| 33 | import Data.List |
|---|
| 34 | import Data.Maybe |
|---|
| 35 | import Data.IORef |
|---|
| 36 | |
|---|
| 37 | import System.IO |
|---|
| 38 | import GHC.Exts |
|---|
| 39 | |
|---|
| 40 | ------------------------------------- |
|---|
| 41 | -- | The :print & friends commands |
|---|
| 42 | ------------------------------------- |
|---|
| 43 | pprintClosureCommand :: GhcMonad m => Bool -> Bool -> String -> m () |
|---|
| 44 | pprintClosureCommand bindThings force str = do |
|---|
| 45 | tythings <- (catMaybes . concat) `liftM` |
|---|
| 46 | mapM (\w -> GHC.parseName w >>= |
|---|
| 47 | mapM GHC.lookupName) |
|---|
| 48 | (words str) |
|---|
| 49 | let ids = [id | AnId id <- tythings] |
|---|
| 50 | |
|---|
| 51 | -- Obtain the terms and the recovered type information |
|---|
| 52 | (subst, terms) <- mapAccumLM go emptyTvSubst ids |
|---|
| 53 | |
|---|
| 54 | -- Apply the substitutions obtained after recovering the types |
|---|
| 55 | modifySession $ \hsc_env -> |
|---|
| 56 | hsc_env{hsc_IC = substInteractiveContext (hsc_IC hsc_env) subst} |
|---|
| 57 | |
|---|
| 58 | -- Finally, print the Terms |
|---|
| 59 | unqual <- GHC.getPrintUnqual |
|---|
| 60 | docterms <- mapM showTerm terms |
|---|
| 61 | liftIO $ (printForUser stdout unqual . vcat) |
|---|
| 62 | (zipWith (\id docterm -> ppr id <+> char '=' <+> docterm) |
|---|
| 63 | ids |
|---|
| 64 | docterms) |
|---|
| 65 | where |
|---|
| 66 | -- Do the obtainTerm--bindSuspensions-computeSubstitution dance |
|---|
| 67 | go :: GhcMonad m => TvSubst -> Id -> m (TvSubst, Term) |
|---|
| 68 | go subst id = do |
|---|
| 69 | let id' = id `setIdType` substTy subst (idType id) |
|---|
| 70 | term_ <- GHC.obtainTermFromId maxBound force id' |
|---|
| 71 | term <- tidyTermTyVars term_ |
|---|
| 72 | term' <- if bindThings && |
|---|
| 73 | False == isUnliftedTypeKind (termType term) |
|---|
| 74 | then bindSuspensions term |
|---|
| 75 | else return term |
|---|
| 76 | -- Before leaving, we compare the type obtained to see if it's more specific |
|---|
| 77 | -- Then, we extract a substitution, |
|---|
| 78 | -- mapping the old tyvars to the reconstructed types. |
|---|
| 79 | let reconstructed_type = termType term |
|---|
| 80 | hsc_env <- getSession |
|---|
| 81 | case (improveRTTIType hsc_env (idType id) (reconstructed_type)) of |
|---|
| 82 | Nothing -> return (subst, term') |
|---|
| 83 | Just subst' -> do { traceOptIf Opt_D_dump_rtti |
|---|
| 84 | (fsep $ [text "RTTI Improvement for", ppr id, |
|---|
| 85 | text "is the substitution:" , ppr subst']) |
|---|
| 86 | ; return (subst `unionTvSubst` subst', term')} |
|---|
| 87 | |
|---|
| 88 | tidyTermTyVars :: GhcMonad m => Term -> m Term |
|---|
| 89 | tidyTermTyVars t = |
|---|
| 90 | withSession $ \hsc_env -> do |
|---|
| 91 | let env_tvs = tyThingsTyVars $ ic_tythings $ hsc_IC hsc_env |
|---|
| 92 | my_tvs = termTyVars t |
|---|
| 93 | tvs = env_tvs `minusVarSet` my_tvs |
|---|
| 94 | tyvarOccName = nameOccName . tyVarName |
|---|
| 95 | tidyEnv = (initTidyOccEnv (map tyvarOccName (varSetElems tvs)) |
|---|
| 96 | , env_tvs `intersectVarSet` my_tvs) |
|---|
| 97 | return$ mapTermType (snd . tidyOpenType tidyEnv) t |
|---|
| 98 | |
|---|
| 99 | -- | Give names, and bind in the interactive environment, to all the suspensions |
|---|
| 100 | -- included (inductively) in a term |
|---|
| 101 | bindSuspensions :: GhcMonad m => Term -> m Term |
|---|
| 102 | bindSuspensions t = do |
|---|
| 103 | hsc_env <- getSession |
|---|
| 104 | inScope <- GHC.getBindings |
|---|
| 105 | let ictxt = hsc_IC hsc_env |
|---|
| 106 | prefix = "_t" |
|---|
| 107 | alreadyUsedNames = map (occNameString . nameOccName . getName) inScope |
|---|
| 108 | availNames = map ((prefix++) . show) [(1::Int)..] \\ alreadyUsedNames |
|---|
| 109 | availNames_var <- liftIO $ newIORef availNames |
|---|
| 110 | (t', stuff) <- liftIO $ foldTerm (nameSuspensionsAndGetInfos availNames_var) t |
|---|
| 111 | let (names, tys, hvals) = unzip3 stuff |
|---|
| 112 | let ids = [ mkVanillaGlobal name ty |
|---|
| 113 | | (name,ty) <- zip names tys] |
|---|
| 114 | new_ic = extendInteractiveContext ictxt (map AnId ids) |
|---|
| 115 | liftIO $ extendLinkEnv (zip names hvals) |
|---|
| 116 | modifySession $ \_ -> hsc_env {hsc_IC = new_ic } |
|---|
| 117 | return t' |
|---|
| 118 | where |
|---|
| 119 | |
|---|
| 120 | -- Processing suspensions. Give names and recopilate info |
|---|
| 121 | nameSuspensionsAndGetInfos :: IORef [String] -> |
|---|
| 122 | TermFold (IO (Term, [(Name,Type,HValue)])) |
|---|
| 123 | nameSuspensionsAndGetInfos freeNames = TermFold |
|---|
| 124 | { |
|---|
| 125 | fSuspension = doSuspension freeNames |
|---|
| 126 | , fTerm = \ty dc v tt -> do |
|---|
| 127 | tt' <- sequence tt |
|---|
| 128 | let (terms,names) = unzip tt' |
|---|
| 129 | return (Term ty dc v terms, concat names) |
|---|
| 130 | , fPrim = \ty n ->return (Prim ty n,[]) |
|---|
| 131 | , fNewtypeWrap = |
|---|
| 132 | \ty dc t -> do |
|---|
| 133 | (term, names) <- t |
|---|
| 134 | return (NewtypeWrap ty dc term, names) |
|---|
| 135 | , fRefWrap = \ty t -> do |
|---|
| 136 | (term, names) <- t |
|---|
| 137 | return (RefWrap ty term, names) |
|---|
| 138 | } |
|---|
| 139 | doSuspension freeNames ct ty hval _name = do |
|---|
| 140 | name <- atomicModifyIORef freeNames (\x->(tail x, head x)) |
|---|
| 141 | n <- newGrimName name |
|---|
| 142 | return (Suspension ct ty hval (Just n), [(n,ty,hval)]) |
|---|
| 143 | |
|---|
| 144 | |
|---|
| 145 | -- A custom Term printer to enable the use of Show instances |
|---|
| 146 | showTerm :: GhcMonad m => Term -> m SDoc |
|---|
| 147 | showTerm term = do |
|---|
| 148 | dflags <- GHC.getSessionDynFlags |
|---|
| 149 | if dopt Opt_PrintEvldWithShow dflags |
|---|
| 150 | then cPprTerm (liftM2 (++) (\_y->[cPprShowable]) cPprTermBase) term |
|---|
| 151 | else cPprTerm cPprTermBase term |
|---|
| 152 | where |
|---|
| 153 | cPprShowable prec t@Term{ty=ty, val=val} = |
|---|
| 154 | if not (isFullyEvaluatedTerm t) |
|---|
| 155 | then return Nothing |
|---|
| 156 | else do |
|---|
| 157 | hsc_env <- getSession |
|---|
| 158 | dflags <- GHC.getSessionDynFlags |
|---|
| 159 | do |
|---|
| 160 | (new_env, bname) <- bindToFreshName hsc_env ty "showme" |
|---|
| 161 | setSession new_env |
|---|
| 162 | -- XXX: this tries to disable logging of errors |
|---|
| 163 | -- does this still do what it is intended to do |
|---|
| 164 | -- with the changed error handling and logging? |
|---|
| 165 | let noop_log _ _ _ _ = return () |
|---|
| 166 | expr = "show " ++ showSDoc (ppr bname) |
|---|
| 167 | _ <- GHC.setSessionDynFlags dflags{log_action=noop_log} |
|---|
| 168 | txt_ <- withExtendedLinkEnv [(bname, val)] |
|---|
| 169 | (GHC.compileExpr expr) |
|---|
| 170 | let myprec = 10 -- application precedence. TODO Infix constructors |
|---|
| 171 | let txt = unsafeCoerce# txt_ |
|---|
| 172 | if not (null txt) then |
|---|
| 173 | return $ Just $ cparen (prec >= myprec && needsParens txt) |
|---|
| 174 | (text txt) |
|---|
| 175 | else return Nothing |
|---|
| 176 | `gfinally` do |
|---|
| 177 | setSession hsc_env |
|---|
| 178 | GHC.setSessionDynFlags dflags |
|---|
| 179 | cPprShowable prec NewtypeWrap{ty=new_ty,wrapped_term=t} = |
|---|
| 180 | cPprShowable prec t{ty=new_ty} |
|---|
| 181 | cPprShowable _ _ = return Nothing |
|---|
| 182 | |
|---|
| 183 | needsParens ('"':_) = False -- some simple heuristics to see whether parens |
|---|
| 184 | -- are redundant in an arbitrary Show output |
|---|
| 185 | needsParens ('(':_) = False |
|---|
| 186 | needsParens txt = ' ' `elem` txt |
|---|
| 187 | |
|---|
| 188 | |
|---|
| 189 | bindToFreshName hsc_env ty userName = do |
|---|
| 190 | name <- newGrimName userName |
|---|
| 191 | let id = AnId $ mkVanillaGlobal name ty |
|---|
| 192 | new_ic = extendInteractiveContext (hsc_IC hsc_env) [id] |
|---|
| 193 | return (hsc_env {hsc_IC = new_ic }, name) |
|---|
| 194 | |
|---|
| 195 | -- Create new uniques and give them sequentially numbered names |
|---|
| 196 | newGrimName :: MonadIO m => String -> m Name |
|---|
| 197 | newGrimName userName = do |
|---|
| 198 | us <- liftIO $ mkSplitUniqSupply 'b' |
|---|
| 199 | let unique = uniqFromSupply us |
|---|
| 200 | occname = mkOccName varName userName |
|---|
| 201 | name = mkInternalName unique occname noSrcSpan |
|---|
| 202 | return name |
|---|
| 203 | |
|---|
| 204 | pprTypeAndContents :: GhcMonad m => Id -> m SDoc |
|---|
| 205 | pprTypeAndContents id = do |
|---|
| 206 | dflags <- GHC.getSessionDynFlags |
|---|
| 207 | let pefas = dopt Opt_PrintExplicitForalls dflags |
|---|
| 208 | pcontents = dopt Opt_PrintBindContents dflags |
|---|
| 209 | pprdId = (pprTyThing pefas . AnId) id |
|---|
| 210 | if pcontents |
|---|
| 211 | then do |
|---|
| 212 | let depthBound = 100 |
|---|
| 213 | -- If the value is an exception, make sure we catch it and |
|---|
| 214 | -- show the exception, rather than propagating the exception out. |
|---|
| 215 | e_term <- gtry $ GHC.obtainTermFromId depthBound False id |
|---|
| 216 | docs_term <- case e_term of |
|---|
| 217 | Right term -> showTerm term |
|---|
| 218 | Left exn -> return (text "*** Exception:" <+> |
|---|
| 219 | text (show (exn :: SomeException))) |
|---|
| 220 | return $ pprdId <+> equals <+> docs_term |
|---|
| 221 | else return pprdId |
|---|
| 222 | |
|---|
| 223 | -------------------------------------------------------------- |
|---|
| 224 | -- Utils |
|---|
| 225 | |
|---|
| 226 | traceOptIf :: GhcMonad m => DynFlag -> SDoc -> m () |
|---|
| 227 | traceOptIf flag doc = do |
|---|
| 228 | dflags <- GHC.getSessionDynFlags |
|---|
| 229 | when (dopt flag dflags) $ liftIO $ printForUser stderr alwaysQualify doc |
|---|