--
-- Pepe Iborra (supported by Google SoC) 2006
--
+-- ToDo: lots of violation of layering here. This module should
+-- decide whether it is above the GHC API (import GHC and nothing
+-- else) or below it.
+--
-----------------------------------------------------------------------------
-module Debugger where
+module Debugger (pprintClosureCommand, showTerm, pprTypeAndContents) where
import Linker
-import Breakpoints
import RtClosureInspect
-import PrelNames
import HscTypes
import IdInfo
--import Id
+import Name
import Var hiding ( varName )
import VarSet
-import VarEnv
import Name
-import NameEnv
-import RdrName
-import Module
-import Finder
import UniqSupply
-import Type
-import TyCon
-import DataCon
-import TcGadt
+import TcType
import GHC
-import GhciMonad
-import PackageConfig
-
+import DynFlags
+import InteractiveEval
import Outputable
-import ErrUtils
-import FastString
import SrcLoc
-import Util
+import PprTyThing
import Control.Exception
import Control.Monad
-import qualified Data.Map as Map
-import Data.Array.Unboxed
-import Data.Traversable ( traverse )
-import Data.Typeable ( Typeable )
+import Data.List
import Data.Maybe
import Data.IORef
import System.IO
import GHC.Exts
-#include "HsVersions.h"
-
------------------------------
--- | The :breakpoint command
------------------------------
-bkptOptions :: String -> GHCi ()
-bkptOptions cmd = do
- dflags <- getDynFlags
- bt <- getBkptTable
- bkptOptions' (words cmd) bt
- where
- bkptOptions' ["list"] bt = do
- let msgs = [ ppr mod <+> colon <+> ppr coords
- | (mod,site) <- btList bt
- , let coords = getSiteCoords bt mod site]
- num_msgs = [parens (int n) <+> msg | (n,msg) <- zip [1..] msgs]
- msg <- showForUser$ if null num_msgs
- then text "There are no enabled breakpoints"
- else vcat num_msgs
- io$ putStrLn msg
-
- bkptOptions' ["stop"] bt = do
- inside_break <- liftM not isTopLevel
- when inside_break $ throwDyn StopChildSession
-
- bkptOptions' ("add":cmds) bt
- | [mod_name,line]<- cmds
- , [(lineNum,[])] <- reads line
- = handleAdd mod_name $ (\mod->addBkptByLine mod lineNum)
-
- | [mod_name,line,col] <- cmds
- = handleAdd mod_name $ (\mod->addBkptByCoord mod (read line, read col))
-
- | otherwise = throwDyn $ CmdLineError $
- "syntax: :breakpoint add Module line [col]"
- where
- handleAdd mod_name f = do
- sess <- getSession
- dflags <- getDynFlags
- mod <- io$ GHC.findModule sess (GHC.mkModuleName mod_name) Nothing
- ghciHandleDyn (handleBkptEx mod) $
- case f mod bt of
- (newTable, site) -> do
- setBkptTable newTable
- io (putStrLn ("Breakpoint set at " ++
- show (getSiteCoords newTable mod site)))
-
- bkptOptions' ("del":cmds) bt
- | [i'] <- cmds
- , [(i,[])] <- reads i'
- , bkpts <- btList bt
- = if i > length bkpts
- then throwDyn $ CmdLineError
- "Not a valid breakpoint #. Use :breakpoint list to see the current breakpoints."
- else
- let (mod, site) = bkpts !! (i-1)
- in handleDel mod $ delBkptBySite mod site
-
- | [fn,line] <- cmds
- , [(lineNum,[])] <- reads line
- , mod <- GHC.mkModule mainPackageId (GHC.mkModuleName fn)
- = handleDel mod $ delBkptByLine mod lineNum
-
- | [fn,line,col] <- cmds
- , [(lineNum,[])] <- reads line
- , [(colNum,[])] <- reads col
- , mod <- GHC.mkModule mainPackageId (GHC.mkModuleName fn)
- = handleDel mod $ delBkptByCoord mod (lineNum, colNum)
-
- | otherwise = throwDyn $ CmdLineError $
- "syntax: :breakpoint del (breakpoint # | Module line [col])"
-
- where delMsg = "Breakpoint deleted"
- handleDel mod f = ghciHandleDyn (handleBkptEx mod) $ do
- modifyBkptTable f
- newTable <- getBkptTable
- sess <- getSession
- dflags <- getDynFlags
- io$ putStrLn delMsg
-
- bkptOptions' _ _ = throwDyn $ CmdLineError $
- "syntax: :breakpoint (list|stop|add|del)"
-
- handleBkptEx :: Module -> Debugger.BkptException -> a
- handleBkptEx _ NoBkptFound = error "No suitable breakpoint site found" --TODO Automatically add to the next suitable line
- handleBkptEx _ NotNeeded = error "Nothing to do"
- handleBkptEx m NotHandled = error$ "Module " ++ showSDoc (ppr m) ++ " was not loaded under debugging mode. Enable debugging mode and reload it"
-
--------------------------
--- Breakpoint Tables
--------------------------
-
-data BkptTable a = BkptTable {
- -- | An array of breaks, indexed by site number
- breakpoints :: Map.Map a (UArray Int Bool)
- -- | A list of lines, each line can have zero or more sites, which are annotated with a column number
- , sites :: Map.Map a [[(SiteNumber, Int)]]
- }
-
-sitesOf :: Ord a => BkptTable a -> a -> Maybe [[(SiteNumber, Int)]]
-sitesOf bt fn = Map.lookup fn (sites bt)
-bkptsOf bt fn = Map.lookup fn (breakpoints bt)
-
-
--- The functions for manipulating BkptTables do throw exceptions
-data BkptException =
- NotHandled
- | NoBkptFound
- | NotNeeded -- Used when a breakpoint was already enabled
- deriving Typeable
-
-emptyBkptTable :: Ord a => BkptTable a
-addModule :: Ord a => a -> [(SiteNumber,Coord)] -> BkptTable a -> BkptTable a
--- | Lines start at index 1
-addBkptByLine :: Ord a => a -> Int -> BkptTable a -> (BkptTable a, SiteNumber)
-addBkptByCoord :: Ord a => a -> Coord -> BkptTable a -> (BkptTable a, SiteNumber)
-delBkptByLine :: Ord a => a -> Int -> BkptTable a -> BkptTable a
-delBkptBySite :: Ord a => a -> SiteNumber -> BkptTable a -> BkptTable a
-delBkptByCoord :: Ord a => a -> Coord -> BkptTable a -> BkptTable a
-
-isBkptEnabled :: Ord a => BkptTable a -> BkptLocation a -> Bool
-btElems :: Ord a => BkptTable a -> [(a, [SiteNumber])]
-btList :: Ord a => BkptTable a -> [BkptLocation a]
-sitesList :: Ord a => BkptTable a -> [(a, [Coord])]
-getSiteCoords :: Ord a => BkptTable a -> a -> SiteNumber -> Coord
-
-emptyBkptTable = BkptTable Map.empty Map.empty
-
-addBkptByLine a i bt
- | Just lines <- sitesOf bt a
- , Just bkptsArr <- bkptsOf bt a
- , i < length lines
- = case lines!!i of
- [] -> throwDyn NoBkptFound
- (x:_) -> let (siteNum,col) = x
- wasAlreadyOn = bkptsArr ! siteNum
- newArr = bkptsArr // [(siteNum, True)]
- newTable = Map.insert a newArr (breakpoints bt)
- in if wasAlreadyOn
- then throwDyn NotNeeded
- else (bt{breakpoints=newTable}, siteNum)
-
- | Just sites <- sitesOf bt a
- = throwDyn NoBkptFound
- | otherwise = throwDyn NotHandled
-
-addBkptByCoord a (r,c) bt
- | Just lines <- sitesOf bt a
- , Just bkptsArr <- bkptsOf bt a
- , r < length lines
- = case [ (sn,col) | (sn,col)<-lines!!r, col>=c] of
- [] -> throwDyn NoBkptFound
- (x:_) -> let (siteNum, col) = x
- wasAlreadyOn = bkptsArr ! siteNum
- newArr = bkptsArr // [(siteNum, True)]
- newTable = Map.insert a newArr (breakpoints bt)
- in if wasAlreadyOn
- then throwDyn NotNeeded
- else (bt{breakpoints=newTable}, siteNum)
-
- | Just sites <- sitesOf bt a
- = throwDyn NoBkptFound
- | otherwise = throwDyn NotHandled
-
-delBkptBySite a i bt
- | Just bkptsArr <- bkptsOf bt a
- , not (inRange (bounds bkptsArr) i)
- = throwDyn NoBkptFound
-
- | Just bkptsArr <- bkptsOf bt a
- , bkptsArr ! i -- Check that there was a enabled bkpt here
- , newArr <- bkptsArr // [(i,False)]
- , newTable <- Map.insert a newArr (breakpoints bt)
- = bt {breakpoints=newTable}
-
- | Just sites <- sitesOf bt a
- = throwDyn NotNeeded
-
- | otherwise = throwDyn NotHandled
-
-delBkptByLine a l bt
- | Just sites <- sitesOf bt a
- , (site:_) <- [s | (s,c') <- sites !! l]
- = delBkptBySite a site bt
-
- | Just sites <- sitesOf bt a
- = throwDyn NoBkptFound
-
- | otherwise = throwDyn NotHandled
-
-delBkptByCoord a (r,c) bt
- | Just sites <- sitesOf bt a
- , (site:_) <- [s | (s,c') <- sites !! r, c>=c', isBkptEnabled bt (a,s)]
- = delBkptBySite a site bt
-
- | Just sites <- sitesOf bt a
- = throwDyn NoBkptFound
-
- | otherwise = throwDyn NotHandled
-
-btElems bt = [ (a, [i | (i,True) <- assocs siteArr])
- | (a, siteArr) <- Map.assocs (breakpoints bt) ]
-
-btList bt = [(a,site) | (a, sites) <- btElems bt, site <- sites]
-
-sitesList bt = [ (a, sitesCoords sitesCols) | (a, sitesCols) <- Map.assocs (sites bt) ]
- where sitesCoords sitesCols =
- [ (row,col)
- | (row, cols) <- zip [0..] sitesCols, (_,col) <- cols ]
-
-getSiteCoords bt a site
- | Just rows <- sitesOf bt a
- = head [ (r,c) | (r,row) <- zip [0..] rows
- , (s,c) <- row
- , s == site ]
-
--- addModule is dumb and inefficient, but it does the job
---addModule fn siteCoords _ | trace ("addModule: " ++ moduleString (unsafeCoerce# fn) ++ " - " ++ show siteCoords) False = undefined
-addModule a [] bt = bt
-addModule a siteCoords bt
- | nrows <- maximum$ [i | (_,(i,j)) <- siteCoords ]
- , sitesByRow <- [ [(s,c) | (s,(r,c)) <- siteCoords, r==i]
- | i <- [0..nrows] ]
- , nsites <- length siteCoords
- , initialBkpts <- listArray (1, nsites) (repeat False)
- = bt{ sites = Map.insert a sitesByRow (sites bt)
- , breakpoints = Map.insert a initialBkpts (breakpoints bt) }
-
-isBkptEnabled bt (a,site)
- | Just bkpts <- bkptsOf bt a
- , inRange (bounds bkpts) site
- = bkpts ! site
- | otherwise = throwDyn NotHandled -- This is an error
-
------------------
--- Other stuff
------------------
-refreshBkptTable :: [ModSummary] -> GHCi ()
-refreshBkptTable [] = return ()
-refreshBkptTable (ms:mod_sums) = do
- sess <- getSession
- when (Opt_Debugging `elem` flags (GHC.ms_hspp_opts ms)) $ do
- old_table <- getBkptTable
- new_table <- addModuleGHC sess old_table (GHC.ms_mod ms)
- setBkptTable new_table
- refreshBkptTable mod_sums
- where addModuleGHC sess bt mod = do
- Just mod_info <- io$ GHC.getModuleInfo sess mod
- dflags <- getDynFlags
- let sites = GHC.modInfoBkptSites mod_info
- io$ debugTraceMsg dflags 2
- (ppr mod <> text ": inserted " <> int (length sites) <>
- text " breakpoints")
- return$ addModule mod sites bt
+-------------------------------------
+-- | The :print & friends commands
+-------------------------------------
+pprintClosureCommand :: Session -> Bool -> Bool -> String -> IO ()
+pprintClosureCommand session bindThings force str = do
+ tythings <- (catMaybes . concat) `liftM`
+ mapM (\w -> GHC.parseName session w >>=
+ mapM (GHC.lookupName session))
+ (words str)
+ let ids = [id | AnId id <- tythings]
+
+ -- Obtain the terms and the recovered type information
+ (terms, substs) <- unzip `liftM` mapM (go session) ids
+
+ -- Apply the substitutions obtained after recovering the types
+ modifySession session $ \hsc_env ->
+ hsc_env{hsc_IC = foldr (flip substInteractiveContext)
+ (hsc_IC hsc_env)
+ (map skolemiseSubst substs)}
+ -- Finally, print the Terms
+ unqual <- GHC.getPrintUnqual session
+ docterms <- mapM (showTerm session) terms
+ (printForUser stdout unqual . vcat)
+ (zipWith (\id docterm -> ppr id <+> char '=' <+> docterm)
+ ids
+ docterms)
+ where
+
+ -- Do the obtainTerm--bindSuspensions-computeSubstitution dance
+ go :: Session -> Id -> IO (Term, TvSubst)
+ go cms id = do
+ term_ <- GHC.obtainTerm cms force id
+ term <- tidyTermTyVars cms term_
+ term' <- if bindThings &&
+ False == isUnliftedTypeKind (termType term)
+ then bindSuspensions cms term
+ else return term
+ -- Before leaving, we compare the type obtained to see if it's more specific
+ -- Then, we extract a substitution,
+ -- mapping the old tyvars to the reconstructed types.
+ let reconstructed_type = termType term
+ subst = unifyRTTI (idType id) (reconstructed_type)
+ return (term',subst)
+
+ tidyTermTyVars :: Session -> Term -> IO Term
+ tidyTermTyVars (Session ref) t = do
+ hsc_env <- readIORef ref
+ let env_tvs = ic_tyvars (hsc_IC hsc_env)
+ my_tvs = termTyVars t
+ tvs = env_tvs `minusVarSet` my_tvs
+ tyvarOccName = nameOccName . tyVarName
+ tidyEnv = (initTidyOccEnv (map tyvarOccName (varSetElems tvs))
+ , env_tvs `intersectVarSet` my_tvs)
+ return$ mapTermType (snd . tidyOpenType tidyEnv) t
+
+-- | Give names, and bind in the interactive environment, to all the suspensions
+-- included (inductively) in a term
+bindSuspensions :: Session -> Term -> IO Term
+bindSuspensions cms@(Session ref) t = do
+ hsc_env <- readIORef ref
+ inScope <- GHC.getBindings cms
+ let ictxt = hsc_IC hsc_env
+ prefix = "_t"
+ alreadyUsedNames = map (occNameString . nameOccName . getName) inScope
+ availNames = map ((prefix++) . show) [(1::Int)..] \\ alreadyUsedNames
+ availNames_var <- newIORef availNames
+ (t', stuff) <- foldTerm (nameSuspensionsAndGetInfos availNames_var) t
+ let (names, tys, hvals) = unzip3 stuff
+ let tys' = map (fst.skolemiseTy) tys
+ let ids = [ mkGlobalId VanillaGlobal name ty vanillaIdInfo
+ | (name,ty) <- zip names tys']
+ new_tyvars = tyVarsOfTypes tys'
+ new_ic = extendInteractiveContext ictxt ids new_tyvars
+ extendLinkEnv (zip names hvals)
+ writeIORef ref (hsc_env {hsc_IC = new_ic })
+ return t'
+ where
+
+-- Processing suspensions. Give names and recopilate info
+ nameSuspensionsAndGetInfos :: IORef [String] ->
+ TermFold (IO (Term, [(Name,Type,HValue)]))
+ nameSuspensionsAndGetInfos freeNames = TermFold
+ {
+ fSuspension = doSuspension freeNames
+ , fTerm = \ty dc v tt -> do
+ tt' <- sequence tt
+ let (terms,names) = unzip tt'
+ return (Term ty dc v terms, concat names)
+ , fPrim = \ty n ->return (Prim ty n,[])
+ , fNewtypeWrap =
+ \ty dc t -> do
+ (term, names) <- t
+ return (NewtypeWrap ty dc term, names)
+ , fRefWrap = \ty t -> do
+ (term, names) <- t
+ return (RefWrap ty term, names)
+ }
+ doSuspension freeNames ct ty hval _name = do
+ name <- atomicModifyIORef freeNames (\x->(tail x, head x))
+ n <- newGrimName name
+ return (Suspension ct ty hval (Just n), [(n,ty,hval)])
+
+
+-- A custom Term printer to enable the use of Show instances
+showTerm :: Session -> Term -> IO SDoc
+showTerm cms@(Session ref) term = do
+ dflags <- GHC.getSessionDynFlags cms
+ if dopt Opt_PrintEvldWithShow dflags
+ then cPprTerm (liftM2 (++) (\_y->[cPprShowable]) cPprTermBase) term
+ else cPprTerm cPprTermBase term
+ where
+ cPprShowable prec t@Term{ty=ty, val=val} =
+ if not (isFullyEvaluatedTerm t)
+ then return Nothing
+ else do
+ hsc_env <- readIORef ref
+ dflags <- GHC.getSessionDynFlags cms
+ do
+ (new_env, bname) <- bindToFreshName hsc_env ty "showme"
+ writeIORef ref (new_env)
+ let noop_log _ _ _ _ = return ()
+ expr = "show " ++ showSDoc (ppr bname)
+ GHC.setSessionDynFlags cms dflags{log_action=noop_log}
+ mb_txt <- withExtendedLinkEnv [(bname, val)]
+ (GHC.compileExpr cms expr)
+ let myprec = 10 -- application precedence. TODO Infix constructors
+ case mb_txt of
+ Just txt_ | txt <- unsafeCoerce# txt_, not (null txt)
+ -> return $ Just$ cparen (prec >= myprec &&
+ needsParens txt)
+ (text txt)
+ _ -> return Nothing
+ `finally` do
+ writeIORef ref hsc_env
+ GHC.setSessionDynFlags cms dflags
+ cPprShowable prec NewtypeWrap{ty=new_ty,wrapped_term=t} =
+ cPprShowable prec t{ty=new_ty}
+ cPprShowable prec RefWrap{wrapped_term=t} = cPprShowable prec t
+ cPprShowable _ _ = return Nothing
+
+ needsParens ('"':_) = False -- some simple heuristics to see whether parens
+ -- are redundant in an arbitrary Show output
+ needsParens ('(':_) = False
+ needsParens txt = ' ' `elem` txt
+
+
+ bindToFreshName hsc_env ty userName = do
+ name <- newGrimName userName
+ let ictxt = hsc_IC hsc_env
+ tmp_ids = ic_tmp_ids ictxt
+ id = mkGlobalId VanillaGlobal name (sigmaType ty) vanillaIdInfo
+ new_ic = ictxt { ic_tmp_ids = id : tmp_ids }
+ return (hsc_env {hsc_IC = new_ic }, name)
+
+-- Create new uniques and give them sequentially numbered names
+newGrimName :: String -> IO Name
+newGrimName userName = do
+ us <- mkSplitUniqSupply 'b'
+ let unique = uniqFromSupply us
+ occname = mkOccName varName userName
+ name = mkInternalName unique occname noSrcSpan
+ return name
+
+pprTypeAndContents :: Session -> [Id] -> IO SDoc
+pprTypeAndContents session ids = do
+ dflags <- GHC.getSessionDynFlags session
+ let pefas = dopt Opt_PrintExplicitForalls dflags
+ pcontents = dopt Opt_PrintBindContents dflags
+ if pcontents
+ then do
+ let depthBound = 100
+ terms <- mapM (GHC.obtainTermB session depthBound False) ids
+ docs_terms <- mapM (showTerm session) terms
+ return $ vcat $ zipWith (\ty cts -> ty <+> equals <+> cts)
+ (map (pprTyThing pefas . AnId) ids)
+ docs_terms
+ else return $ vcat $ map (pprTyThing pefas . AnId) ids