Skip to content

Commit 375e6aa

Browse files
committed
[Minor] Trace correlations now parsed from trace module. Fixes #92
1 parent b28331d commit 375e6aa

File tree

8 files changed

+94
-120
lines changed

8 files changed

+94
-120
lines changed

src/Endemic/Check.hs

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ baseFun nm val =
6969
elb :: LHsLocalBinds GhcPs
7070
elb = noLoc $ EmptyLocalBinds NoExtField
7171

72+
-- | Unpacks a function defined by baseFun
73+
unFun :: HsBind GhcPs -> Maybe (LHsExpr GhcPs)
74+
unFun (FunBind _ _ (MG _ ((L _ [L _ (Match _ _ _ (GRHSs _ [L _ (GRHS _ _ ex)] _))])) _) _ _) = Just ex
75+
unFun _ = Nothing
76+
7277
-- Shorthands for common constructs
7378

7479
-- | Short for "the function"

src/Endemic/Configuration/Types.hs

+2-5
Original file line numberDiff line numberDiff line change
@@ -415,14 +415,11 @@ data AdditionalConf = AddConf
415415
{ -- | If assumeNoLoops is true, we check the fixes assuming that there
416416
-- are no loops in the generated code. Beware! Will die with a message
417417
-- saying Alarm Clock in case there were loops.
418-
assumeNoLoops :: Bool,
419-
-- | Allow unfound hoes means that we can ignore holes that are not in
420-
-- the trace_correlation
421-
allowUnfoundHoles :: Bool
418+
assumeNoLoops :: Bool
422419
}
423420

424421
instance Default AdditionalConf where
425-
def = AddConf {assumeNoLoops = True, allowUnfoundHoles = True}
422+
def = AddConf {assumeNoLoops = True}
426423

427424
-- | The Problem Description is generated at runtime, descriping a particular
428425
-- program to fix.

src/Endemic/Eval.hs

+53-76
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Data.Dynamic (Dynamic, fromDynamic)
4242
import Data.Function (on)
4343
import Data.IORef (IORef, newIORef, readIORef)
4444
import Data.List (groupBy, intercalate, nub, partition, stripPrefix)
45+
import Data.Map (Map)
4546
import qualified Data.Map as Map
4647
import Data.Maybe (catMaybes, isJust, isNothing, mapMaybe)
4748
import Data.Set (Set)
@@ -55,7 +56,7 @@ import DynFlags
5556
import Endemic.Check
5657
import Endemic.Configuration
5758
import Endemic.Plugin
58-
import Endemic.Traversals (flattenExpr)
59+
import Endemic.Traversals (flattenExpr, sanctifyExpr)
5960
import Endemic.Types
6061
import Endemic.Util
6162
import ErrUtils (ErrMsg (errMsgSeverity), errMsgSpan, pprErrMsgBagWithLoc)
@@ -67,6 +68,7 @@ import GHC.Prim (unsafeCoerce#)
6768
import GhcPlugins hiding (exprType)
6869
import Numeric (showHex)
6970
import PrelNames (toDynName)
71+
import qualified Pretty as Pretty
7072
import RnExpr (rnLExpr)
7173
import StringBuffer (stringToStringBuffer)
7274
import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, makeAbsolute, removeDirectory, removeDirectoryRecursive, removeFile)
@@ -649,44 +651,6 @@ moduleToProb baseCC@CompConf {tempDirBase = baseTempDir, ..} mod_path mb_target
649651
-- _ -> int_prob
650652
return (cc', tc_modul, int_prob)
651653

652-
-- Create a fake base loc for a trace.
653-
fakeBaseLoc :: CompileConfig -> EProblem -> EProgFix -> Ghc SrcSpan
654-
fakeBaseLoc cc prob (fix : _) = getLoc . head <$> buildTraceCorrelExpr cc prob fix
655-
fakeBaseLoc cc prob [] = error "Cannot construct base loc for empty prog!"
656-
657-
-- When we do the trace, we use a "fake_target" function. This build the
658-
-- corresponding expression,
659-
buildTraceCorrelExpr :: CompileConfig -> EProblem -> EExpr -> Ghc [LHsExpr GhcPs]
660-
buildTraceCorrelExpr cc EProb {..} expr = do
661-
let correl =
662-
map (\(nm, _, _) -> baseFun (mkVarUnqual $ fsLit $ "fake_target_" ++ rdrNamePrint nm) expr) e_prog
663-
correl_ctxts :: [LHsLocalBinds GhcPs]
664-
correl_ctxts = map (\c -> noLoc $ HsValBinds NoExtField (ValBinds NoExtField (unitBag c) [])) correl
665-
correl_exprs :: [LHsExpr GhcPs]
666-
correl_exprs = map (\ctxt -> noLoc $ HsLet NoExtField ctxt (noLoc hole)) correl_ctxts
667-
668-
pcorrels <- mapM (parseExprNoInit . showUnsafe) correl_exprs
669-
let getBod (L _ (HsLet _ (L _ (HsValBinds _ (ValBinds _ bg _))) _))
670-
| [L _ FunBind {fun_matches = MG {mg_alts = (L _ alts)}}] <- bagToList bg,
671-
[L _ Match {m_grhss = GRHSs {grhssGRHSs = [L _ (GRHS _ _ bod)]}}] <- alts =
672-
Just bod
673-
getBod _ = Nothing
674-
return $ mapMaybe getBod pcorrels
675-
buildTraceCorrelExpr _ _ _ = error "External fixes not supported!"
676-
677-
-- We build a Map from the traced expression and to the original so we can
678-
-- correlate the trace information with the expression we're checking.
679-
-- This helps e.g. to find placements of changed elements (in our fixes) to the original position.
680-
buildTraceCorrel :: CompileConfig -> EProblem -> EExpr -> Ghc [Map.Map SrcSpan SrcSpan]
681-
buildTraceCorrel cc prob expr = do
682-
map toCorrel <$> buildTraceCorrelExpr cc prob expr
683-
where
684-
expr_exprs = flattenExpr expr
685-
toCorrel trace_expr = Map.fromList $ filter (\(e, b) -> isGoodSrcSpan e && isGoodSrcSpan b) locPairs
686-
where
687-
trace_exprs = flattenExpr trace_expr
688-
locPairs = zipWith (\t e -> (getLoc t, getLoc e)) trace_exprs expr_exprs
689-
690654
-- | Runs a GHC action and cleans up any hpc-directories that might have been
691655
-- created as a side-effect.
692656
-- TODO: Does this slow things down massively? We lose out on the pre-generated
@@ -762,12 +726,22 @@ traceTarget ::
762726
EProp ->
763727
[RExpr] ->
764728
IO (Maybe TraceRes)
765-
traceTarget cc tp e fp ce = head <$> (traceTargets cc tp e [(fp, ce)])
729+
traceTarget cc tp e fp ce = head <$> traceTargets cc tp e [(fp, ce)]
766730

767-
toInvokes :: Trace -> Map.Map SrcSpan Integer
768-
toInvokes res = Map.fromList $ map only_max $ flatten res
731+
toInvokes :: Trace -> Map.Map (EExpr, SrcSpan) Integer
732+
toInvokes (ex, res) = Map.fromList $ mapMaybe only_max $ flatten res
769733
where
770-
only_max (src, r) = (mkInteractive src, maximum $ map snd r)
734+
only_max :: (SrcSpan, [(BoxLabel, Integer)]) -> Maybe ((EExpr, SrcSpan), Integer)
735+
-- TODO: What does it mean if there are multiple labels here?
736+
only_max (src, xs)
737+
| xboxes <- filter isXBox xs,
738+
not (null xboxes),
739+
s <- maximum (map snd xboxes) =
740+
Just ((ex, src), s)
741+
where
742+
isXBox (ExpBox _, _) = True
743+
isXBox _ = False
744+
only_max _ = Nothing
771745

772746
-- Run HPC to get the trace information.
773747
traceTargets ::
@@ -779,19 +753,13 @@ traceTargets ::
779753
traceTargets _ _ _ [] = return []
780754
-- Note: we call runGhc' here directly, since we want every trace to be a
781755
-- new ghc thread (since we set the dynflags per module)
782-
traceTargets cc@CompConf {..} tp@EProb {..} exprs' ps_w_ce =
756+
traceTargets cc@CompConf {..} tp@EProb {..} exprs ps_w_ce =
783757
runGhc' cc $ do
784758
seed <- liftIO newSeed
785-
let traceHash = flip showHex "" $ abs $ hashString $ showSDocUnsafe $ ppr (exprs', ps_w_ce, seed)
759+
let traceHash = flip showHex "" $ abs $ hashString $ showSDocUnsafe $ ppr (exprs, ps_w_ce, seed)
786760
tempDir = tempDirBase </> "trace" </> traceHash
787761
the_f = tempDir </> ("FakeTraceTarget" ++ traceHash) <.> "hs"
788762
liftIO $ createDirectoryIfMissing True tempDir
789-
(exprs, realSpan) <- case exprs' of
790-
((L (RealSrcSpan realSpan) _) : _) -> return (exprs', realSpan)
791-
_ -> do
792-
~tl@(RealSrcSpan rsp) <- fakeBaseLoc cc tp exprs'
793-
let exprs = map (L tl . unLoc) exprs'
794-
return (exprs, rsp)
795763
let correl =
796764
zipWith
797765
( \(nm, _, _) e ->
@@ -801,33 +769,11 @@ traceTargets cc@CompConf {..} tp@EProb {..} exprs' ps_w_ce =
801769
e_prog
802770
exprs
803771
fake_target_names = Set.fromList $ map (\(n, _, _) -> n) correl
804-
toDom :: (TixModule, Mix) -> [MixEntryDom [(BoxLabel, Integer)]]
805-
toDom (TixModule _ _ _ ts, Mix _ _ _ _ es) =
806-
createMixEntryDom $ zipWith (\t (pos, bl) -> (pos, (bl, t))) ts es
807-
isTarget Node {rootLabel = (_, [(TopLevelBox [ftn], _)])} =
808-
ftn `Set.member` fake_target_names
809-
isTarget _ = False
810-
-- We convert the HpcPos to the equivalent span we would get if we'd
811-
-- parsed and compiled the expression directly.
812-
toFakeSpan :: FilePath -> HpcPos -> HpcPos -> SrcSpan
813-
toFakeSpan the_f root sp = mkSrcSpan start end
814-
where
815-
fname = fsLit $ takeFileName the_f
816-
(_, _, rel, rec) = fromHpcPos root
817-
eloff = rel - srcSpanEndLine realSpan
818-
ecoff = rec - srcSpanEndCol realSpan
819-
(sl, sc, el, ec) = fromHpcPos sp
820-
-- We add two spaces before every line in the source.
821-
start = mkSrcLoc fname (sl - eloff) (sc - ecoff -1)
822-
-- GHC Srcs end one after the end
823-
end = mkSrcLoc fname (el - eloff) (ec - ecoff)
824772
mname = filter isAlphaNum $ dropExtension $ takeFileName the_f
825773
modTxt = exprToTraceModule cc tp seed mname correl ps_w_ce
826774
exeName = dropExtension the_f
827775
mixFilePath = tempDir
828-
timeoutVal = fromIntegral timeout
829776

830-
liftIO $ logStr DEBUG modTxt
831777
liftIO $ writeFile the_f modTxt
832778
_ <- liftIO $ mapM (logStr DEBUG) $ lines modTxt
833779
-- We set the module as the main module, which makes GHC generate
@@ -858,6 +804,18 @@ traceTargets cc@CompConf {..} tp@EProb {..} exprs' ps_w_ce =
858804

859805
_ <- load (LoadUpTo target_name)
860806

807+
modul@ParsedModule {pm_parsed_source = (L _ HsModule {..})} <-
808+
getModSummary target_name >>= parseModule
809+
-- Retrieves the Values declared in the given Haskell-Module
810+
let our_targets :: Map String (LHsExpr GhcPs)
811+
our_targets = Map.fromList $ mapMaybe fromValD hsmodDecls
812+
where
813+
fromValD (L l (ValD _ b@FunBind {..}))
814+
| rdrNameToStr (unLoc fun_id) `elem` fake_target_names,
815+
Just ex <- unFun b =
816+
Just (rdrNameToStr $ unLoc fun_id, ex)
817+
fromValD _ = Nothing
818+
861819
-- We should for here in case it doesn't terminate, and modify
862820
-- the run function so that it use the trace reflect functionality
863821
-- to timeout and dump the tix file if possible.
@@ -908,21 +866,40 @@ traceTargets cc@CompConf {..} tp@EProb {..} exprs' ps_w_ce =
908866
-- TODO: When run in parallel, this can fail
909867
let rm m = (m,) <$> readMix [mixFilePath] (Right m)
910868
isTargetMod = (mname ==) . tixModuleName
869+
toDom :: (TixModule, Mix) -> [MixEntryDom [(BoxLabel, Integer)]]
870+
toDom (TixModule _ _ _ ts, Mix _ _ _ _ es) =
871+
createMixEntryDom $ zipWith (\t (pos, bl) -> (pos, (bl, t))) ts es
872+
873+
nd n@Node {rootLabel = (root, _)} =
874+
fmap (Data.Bifunctor.first (toSpan the_f)) n
875+
wTarget n@Node {rootLabel = (_, [(TopLevelBox [ftn], _)])} =
876+
(,nd n) <$> our_targets Map.!? ftn
877+
wTarget _ = Nothing
878+
-- We convert the HpcPos to the equivalent span we would get if we'd
879+
-- parsed and compiled the expression directly.
880+
toSpan :: FilePath -> HpcPos -> SrcSpan
881+
toSpan the_f sp = mkSrcSpan start end
882+
where
883+
fname = fsLit the_f
884+
(sl, sc, el, ec) = fromHpcPos sp
885+
-- We add two spaces before every line in the source.
886+
start = mkSrcLoc fname sl sc
887+
end = mkSrcLoc fname el (ec + 1)
911888
case tix of
912889
Just (Tix mods) -> do
913890
-- We throw away any extra functions in the file, such as
914891
-- the properties and the main function, and only look at
915892
-- the ticks for our expression
916893
let fake_only = filter isTargetMod mods
917-
nd n@Node {rootLabel = (root, _)} =
918-
fmap (Data.Bifunctor.first (toFakeSpan the_f root)) n
919-
res <- map nd . filter isTarget . concatMap toDom <$> mapM rm fake_only
894+
res <- mapMaybe wTarget . concatMap toDom <$> mapM rm fake_only
920895
return $ Just res
921896
_ -> return Nothing
922897

923898
res <- liftIO $ mapM (runTrace . fst) $ zip [0 ..] ps_w_ce
924899
cleanupAfterLoads tempDir mname dynFlags
925900
return res
901+
where
902+
timeoutVal = fromIntegral timeout
926903
traceTargets _ _ [] _ = error "No fix!"
927904
traceTargets _ ExProb {} _ _ = error "Exprobs not supported!"
928905

src/Endemic/Repair.hs

+17-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE NumericUnderscores #-}
55
{-# LANGUAGE OverloadedStrings #-}
66
{-# LANGUAGE RecordWildCards #-}
7+
{-# LANGUAGE ScopedTypeVariables #-}
78
{-# LANGUAGE TupleSections #-}
89
{-# LANGUAGE TypeApplications #-}
910

@@ -40,6 +41,7 @@ import Data.Char (isAlphaNum)
4041
import Data.Default
4142
import Data.Dynamic (fromDyn)
4243
import Data.Either (lefts, rights)
44+
import Data.Foldable (find)
4345
import Data.Function (on)
4446
import Data.Functor (($>), (<&>))
4547
import qualified Data.Functor
@@ -428,7 +430,7 @@ findEvaluatedHoles
428430
resolve = map $ (expr_map IntMap.!) *** map (first (eprop_map Map.!))
429431

430432
-- traces that worked are all non-empty traces that are sufficiently mapped to exprs and props
431-
traces_that_worked <-
433+
traces_that_worked :: [(EExpr, Map (EExpr, SrcSpan) Integer)] <-
432434
liftIO $
433435
map (second (Map.unionsWith (+) . map (toInvokes . snd)))
434436
-- The traces are per prop per expr, but we need a per expr per prop,
@@ -445,26 +447,21 @@ findEvaluatedHoles
445447
-- We then remove suggested holes that are unlikely to help (naively for now
446448
-- in the sense that we remove only holes which did not get evaluated at all,
447449
-- so they are definitely not going to matter).
448-
let fk (expr, invokes) | non_zero <- Map.keysSet (Map.filter (> 0) invokes),
449-
not (null non_zero) = do
450-
liftIO $ logStr DEBUG "Building trace correlation..."
451-
trace_correls_per_target <- buildTraceCorrel cc tp expr
452-
453-
let non_zero_src = Set.unions $ Set.map lookupInCorrel non_zero
454-
lookupInCorrel el =
455-
case mapMaybe (Map.lookup el) trace_correls_per_target of
456-
-- TODO: This should never happen
457-
[] ->
458-
if allowUnfoundHoles
459-
then Set.empty
460-
else error "Shouldn't happen!"
461-
xs -> Set.fromList xs
462-
return $ filter ((`Set.member` non_zero_src) . fst) $ sanctifyExpr expr
463-
fk _ = return []
464-
nubOrd = Set.toList . Set.fromList
465-
non_zero_holes <- mapM fk traces_that_worked
450+
let fk :: (EExpr, Map (EExpr, SrcSpan) Integer) -> Set.Set (SrcSpan, LHsExpr GhcPs)
451+
fk (expr, invokes)
452+
| non_zero <- Map.keysSet (Map.filter (> 0) invokes),
453+
not (null non_zero) =
454+
Set.map toExprHole non_zero
455+
where
456+
sfe = sanctifyExpr expr
457+
toExprHole (iv_expr, iv_loc) =
458+
case find ((== iv_loc) . fst . snd) (zip sfe (sanctifyExpr iv_expr)) of
459+
Just r -> fst r
460+
_ -> error "SHOULND'T HAPPEN"
461+
fk _ = Set.empty
462+
non_zero_holes = Set.unions $ map fk traces_that_worked
466463
-- nubOrd deduplicates collections of sortable items. it's faster than other dedups.
467-
let nzh = nubOrd $ concat non_zero_holes
464+
let nzh = Set.toList non_zero_holes
468465
liftIO $ logStr DEBUG $ "Found " ++ show (length nzh) ++ " evaluated holes"
469466
return nzh
470467
findEvaluatedHoles _ = error "Cannot find evaluated holes of external problems yet!"

src/Endemic/Types.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ isFixed :: TestSuiteResult -> Bool
7979
isFixed (Right x) = x
8080
isFixed (Left xs) = and xs
8181

82-
type Trace = Tree (SrcSpan, [(BoxLabel, Integer)])
82+
type Trace = (LHsExpr GhcPs, Tree (SrcSpan, [(BoxLabel, Integer)]))
8383

8484
type TraceRes = [Trace]
8585

src/Endemic/Util.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,8 @@ propToName :: EProp -> String
379379
propToName (L _ FunBind {..}) = rdrNameToStr $ unLoc fun_id
380380
propToName _ = error "Non-prop passed to propToName!"
381381

382-
traceOutMsg :: Outputable p => String -> p -> p
383-
traceOutMsg msg a = trace (msg ++ " { " ++ showSDocUnsafe (ppr a) ++ " } ") a
382+
traceOutId :: Outputable p => String -> p -> p
383+
traceOutId msg a = trace (msg ++ " { " ++ showSDocUnsafe (ppr a) ++ " } ") a
384384

385385
traceOut :: Outputable p => String -> p -> a -> a
386386
traceOut msg a = trace (msg ++ " { " ++ showSDocUnsafe (ppr a) ++ " } ")

tests/SlowTests.hs

+8-8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import Endemic.Search.Exhaustive
2222
import Endemic.Search.PseudoGenetic (pseudoGeneticRepair)
2323
import Endemic.Traversals
2424
import Endemic.Types
25-
import Endemic.Util (logStr, withLogLevel)
25+
import Endemic.Util (logStr, traceOut, traceOutId, withLogLevel)
26+
import GhcPlugins (text)
2627
import Test.Tasty
2728
import Test.Tasty.ExpectedFailure
2829
import Test.Tasty.HUnit
@@ -166,13 +167,12 @@ specialTests =
166167
60_000_000
167168
"Issue 87 w/o function fits"
168169
"tests/cases/Issue87.hs",
169-
expectFail $
170-
mkRepairTest
171-
tESTCONF
172-
(runGenRepair . \desc -> desc {addConf = (addConf desc) {allowUnfoundHoles = False}})
173-
60_000_000
174-
"Issue 92"
175-
"tests/cases/BrokenModule.hs"
170+
mkRepairTest
171+
tESTCONF
172+
runGenRepair
173+
60_000_000
174+
"Issue 92"
175+
"tests/cases/BrokenModule.hs"
176176
]
177177

178178
refinementTests :: TestTree

tests/Tests.hs

+6-8
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,10 @@ traceTests =
344344
[failed_prop] <- failingProps cc tp
345345
Just counter_example <- propCounterExample cc tp failed_prop
346346
let eprog_fix = eProgToEProgFix e_prog
347-
Just [Node {subForest = [tree@Node {rootLabel = (tl, tname)}]}] <-
347+
Just [(texp, Node {subForest = [tree@Node {rootLabel = (tl, tname)}]})] <-
348348
traceTarget cc tp eprog_fix failed_prop counter_example
349349
expr <- runJustParseExpr cc wrong_prog
350-
getLoc expr @?= mkInteractive tl
350+
showUnsafe expr @?= showUnsafe texp
351351
all ((== 1) . snd) (concatMap snd $ flatten tree) @? "All subexpressions should be touched only once!",
352352
localOption (mkTimeout 30_000_000) $
353353
testCase "Trace finds loop" $ do
@@ -382,12 +382,10 @@ traceTests =
382382
let [(_, e_ty, e_prog')] = e_prog
383383
prog_at_ty = progAtTy e_prog' e_ty
384384
eprog_fix = eProgToEProgFix $ applyFixToEProg e_prog mempty
385-
[tcorrel] <- runGhc' cc $ buildTraceCorrel cc tp prog_at_ty
386-
Just [res] <- traceTarget cc tp eprog_fix failed_prop counter_example_args
387-
let eMap = Map.fromList $ map (getLoc &&& showUnsafe) $ flattenExpr prog_at_ty
388-
chain l = tcorrel Map.!? l >>= (eMap Map.!?)
389-
trc = map (\(s, r) -> (chain $ mkInteractive s, r, maximum $ map snd r)) $ flatten res
390-
isXbox (ExpBox _) = True
385+
Just [(texp, res)] <- traceTarget cc tp eprog_fix failed_prop counter_example_args
386+
let eMap = Map.fromList $ map (getLoc &&& showUnsafe) $ flattenExpr texp
387+
trc = map (\(s, r) -> (eMap Map.!? s, r, maximum $ map snd r)) $ flatten res
388+
isXBox (ExpBox _) = True
391389
isXBox _ = False
392390
isInEMapOrNotExpBox (Just _, _, _) = True
393391
isInEMapOrNotExpBox (_, r, _) = not (any (isXBox . fst) r)

0 commit comments

Comments
 (0)