Skip to content

Commit

Permalink
Merge pull request #452 from GaloisInc/issue-451
Browse files Browse the repository at this point in the history
Issue #451
  • Loading branch information
danmatichuk authored Oct 14, 2024
2 parents 8c154e3 + 1cb3761 commit 6d0d70d
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 112 deletions.
1 change: 1 addition & 0 deletions pate.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ library
Pate.Script,
Pate.Timeout,
Pate.TraceTree,
Pate.TraceCollection,
Pate.TraceConstraint,
Pate.ExprMappable,
What4.ExprHelpers,
Expand Down
76 changes: 74 additions & 2 deletions src/Pate/Monad.hs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ module Pate.Monad
, fnTrace
, getWrappedSolver
, joinPatchPred
, withSharedEnvEmit
, module PME
, atPriority, currentPriority, thisPriority)
where
Expand All @@ -111,7 +112,7 @@ import GHC.Stack ( HasCallStack, callStack )

import Control.Lens ( (&), (.~) )
import qualified Control.Monad.Fail as MF
import Control.Monad (void)
import Control.Monad (void, forM)
import Control.Monad.IO.Class (liftIO)
import qualified Control.Monad.IO.Unlift as IO
import qualified Control.Concurrent as IO
Expand All @@ -120,6 +121,7 @@ import Control.Monad.Catch
import qualified Control.Monad.Reader as CMR
import Control.Monad.Reader ( asks, ask )
import Control.Monad.Except
import Control.Monad.State (StateT(..), get, put, evalStateT, modify, execStateT)

import Data.Maybe ( fromMaybe )
import qualified Data.Map as M
Expand Down Expand Up @@ -197,6 +199,11 @@ import Data.Functor.Const (Const(..))
import Unsafe.Coerce (unsafeCoerce)
import Debug.Trace
import Data.List
import Data.Parameterized.SymbolRepr
import qualified What4.JSON as W4S
import qualified Data.Aeson as JSON
import qualified Data.Aeson.Text as JSON
import qualified GHC.IO.Unsafe as IO

atPriority ::
NodePriority ->
Expand Down Expand Up @@ -402,7 +409,72 @@ printExprTruncated (ExprLabel lbl) (Some e) =
[] -> pfx
[a] -> pfx <> PP.pretty a
(a:as) -> pfx <> PP.pretty a <> ".." <> PP.pretty (last as)


type IsSerializableNode sym arch nm = (IsTraceNode '(sym, arch) nm, W4S.W4Serializable sym (TraceNodeLabel nm), W4S.W4Serializable sym (TraceNodeType '(sym, arch) nm))

data W4SerializableNode sym arch nm = IsSerializableNode sym arch nm =>
W4SerializableNode (SymbolRepr nm) (TraceNodeLabel nm) (TraceNodeType '(sym, arch) nm)

-- | List of trace nodes (of any type) that share the same expression binding
-- environment when serialized
data SharedExprEnv sym arch = SharedExprEnv sym [Some (W4SerializableNode sym arch)]

instance ValidSymArch sym arch => IsTraceNode '(sym,arch) "shared_env" where
type TraceNodeType '(sym,arch) "shared_env" = SharedExprEnv sym arch
type TraceNodeLabel "shared_env" = String

prettyNode top_lbl (SharedExprEnv _ nds) =
PP.vsep $ (PP.pretty top_lbl <> ": "):(map (\(Some (W4SerializableNode (_ :: SymbolRepr nm) lbl nd)) -> prettyNode @_ @'(sym,arch) @nm lbl nd) nds)

jsonNode sym top_lbl (SharedExprEnv _ nds) = do
(\f -> evalStateT f W4S.emptyExprCache) $ do
contents <- forM nds $ \(Some (W4SerializableNode nm lbl nd)) -> do
cache0 <- get
(lbl_json, cache1) <- liftIO $ W4S.w4ToJSONWithCache sym cache0 lbl
(nd_json, cache2) <- liftIO $ W4S.w4ToJSONWithCache sym cache1 nd
put cache2
return $ JSON.object [ "type" JSON..= symbolRepr nm, "label" JSON..= lbl_json, "value" JSON..= nd_json ]
cache <- get
env <- liftIO $ W4S.cacheToEnv sym cache
env_json <- liftIO $ W4S.serializeExprEnv sym env
return $ JSON.object [ "name" JSON..= top_lbl, "shared_env" JSON..= env_json, "contents" JSON..= contents]

nodeTags = [(Summary, prettyNode @_ @'(sym,arch) @"shared_env")
-- fixme: quick hack to get JSON out without needing to run the entire
-- toplevel in JSON mode. Ideally this would be something that the Repl could
-- do itself.
, ("JSON", \top_lbl env@(SharedExprEnv sym _) ->
let v = IO.unsafePerformIO $ jsonNode @_ @'(sym,arch) @"shared_env" sym top_lbl env
in PP.pretty $ JSON.encodeToLazyText v)
]


-- | Emit multiple values to the tracetree with a common expression binding environment
-- This will ultimately result in a single composite value, where all of the given values are serialized
-- using the same expression binding cache.
--
--
-- FIXME: this is a bit of a gross hack to work around the fact that we can't supply
-- an expression binding environment when emitting a value via 'emitTrace'.
-- What we need is a common datatype for expression binding environments that both
-- TraceTree and W4Serializable use so we can do this more sensibly (i.e. have a IsTreeBuilder primitive that
-- collects all of the inner traces and builds a common binding environment from them)
--
-- For now we can at least abstract away the details of this packaging by just taking
-- an arbitrary monadic operation that collects the individual traces.

withSharedEnvEmit ::
forall sym arch.
String ->
(forall m. Monad m => (forall nm. (IsSerializableNode sym arch nm) => SymbolRepr nm -> TraceNodeLabel nm -> TraceNodeType '(sym,arch) nm -> m ()) -> m ()) ->
EquivM sym arch ()
withSharedEnvEmit top_nm f = withSym $ \sym -> do
env <- execStateT (f g) (SharedExprEnv sym [])
emitTraceLabel @"shared_env" @'(sym,arch) top_nm env
where
g :: forall (nm :: Symbol). IsSerializableNode sym arch nm => SymbolRepr nm -> TraceNodeLabel nm -> TraceNodeType '(sym,arch) nm -> StateT (SharedExprEnv sym arch) (EquivM_ sym arch) ()
g nm lbl v = modify $ \(SharedExprEnv sym vs) -> SharedExprEnv sym ((Some (W4SerializableNode nm lbl v)):vs)

withBinary ::
forall bin sym arch a.
PBi.KnownBinary bin =>
Expand Down
206 changes: 206 additions & 0 deletions src/Pate/TraceCollection.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
{-|
Module : Pate.TraceCollection
Copyright : (c) Galois, Inc 2024
Maintainer : Daniel Matichuk <[email protected]>
Specialized map that relates memory cells (see 'Pate.MemCell') and registers
to traces. Used during widening (see 'Pate.Verification.Widening') to associate
location that are widened in an equivalence domain to a trace that demonstrates
why the widening was necessary (i.e. counter-example for how that location could
be made inequivalent).
-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Pate.TraceCollection
( TraceCollection
, empty
, lookupReg
, insertReg
, lookupCell
, insertCell
, insert
, lookup
) where

import Prelude hiding ( lookup )

import qualified Prettyprinter as PP

import Data.Maybe
import qualified Data.Set as Set
import Data.Set ( Set )

import qualified Data.Map as Map
import Data.Map ( Map )

import qualified Data.Text as T

import qualified Data.Macaw.CFG as MM

import Data.Parameterized.Classes()
import Data.Parameterized.Some

import qualified Pate.Arch as PA
import Pate.TraceTree
import qualified Pate.MemCell as PMC
import qualified Pate.Solver as PSo
import qualified Pate.Verification.StrongestPosts.CounterExample as CE

import qualified What4.JSON as W4S
import qualified Data.Aeson as JSON


-- | A map that associates locations ('MM.ArchReg' or 'PMC.MemCell') with traces
-- ('CE.TraceEvents'). Each location is mapped to a set of indexes into
-- a list of traces. These indexes are used during serialization ('W4S.W4Serializable') to
-- avoid duplication when one trace is shared by multiple locations.
data TraceCollection sym arch = TraceCollection
{
trAllTraces :: [CE.TraceEvents sym arch]
, trTraceMapRegs :: Map (Some (MM.ArchReg arch)) (Set Int)
-- ^ mapping from registers into indexes in to the list of all traces
, trTraceMapCells :: Map (Some (PMC.MemCell sym arch)) (Set Int)
-- ^ mapping from memory cells into indexes in to the list of all traces
}

empty :: TraceCollection sym arch
empty = TraceCollection [] Map.empty Map.empty

-- | Add a new trace to the set of traces associated with the given 'MM.ArchReg'
insertReg ::
PA.ValidArch arch =>
MM.ArchReg arch tp ->
CE.TraceEvents sym arch ->
TraceCollection sym arch ->
TraceCollection sym arch
insertReg reg tr trcol = trcol
{ trAllTraces = tr:(trAllTraces trcol)
, trTraceMapRegs = Map.insertWith Set.union (Some reg) (Set.singleton (length (trAllTraces trcol))) (trTraceMapRegs trcol)
}

-- | Add a new trace to the set of traces associated with the given 'PMC.MemCell'
insertCell ::
PSo.ValidSym sym =>
PMC.MemCell sym arch w ->
CE.TraceEvents sym arch ->
TraceCollection sym arch ->
TraceCollection sym arch
insertCell cell tr trcol = trcol
{ trAllTraces = tr:(trAllTraces trcol)
, trTraceMapCells = Map.insertWith Set.union (Some cell) (Set.singleton (length (trAllTraces trcol))) (trTraceMapCells trcol)
}

-- | Get all traces associated with the given 'MM.ArchReg'
lookupReg ::
PA.ValidArch arch =>
TraceCollection sym arch ->
MM.ArchReg arch tp ->
[CE.TraceEvents sym arch]
lookupReg trcol reg = case Map.lookup (Some reg) (trTraceMapRegs trcol) of
Just idxs -> map (\i -> (trAllTraces trcol) !! i) (Set.toList idxs)
Nothing -> []

-- | Get all traces associated with the given 'PMC.MemCell'
lookupCell ::
(PSo.ValidSym sym, PA.ValidArch arch) =>
TraceCollection sym arch ->
PMC.MemCell sym arch w ->
[CE.TraceEvents sym arch]
lookupCell trcol cell = case Map.lookup (Some cell) (trTraceMapCells trcol) of
Just idxs -> map (\i -> (trAllTraces trcol) !! i) (Set.toList idxs)
Nothing -> []

-- | Add a single trace to the set of traces associated with the given
-- list of registers and memory locations. Note that although this
-- is functionally equivalent to folding via 'insertReg' and 'insertCell',
-- the resulting JSON from serialization (via 'W4S.W4Serializable.w4Serialize')
-- only contains one copy of the given trace.
insert ::
PSo.ValidSym sym =>
PA.ValidArch arch =>
[Some (MM.ArchReg arch)] ->
[Some (PMC.MemCell sym arch)] ->
CE.TraceEvents sym arch ->
TraceCollection sym arch ->
TraceCollection sym arch
insert regs cells tr trcol = trcol
{ trAllTraces = tr:(trAllTraces trcol)
, trTraceMapRegs =
foldr (\reg -> Map.insertWith Set.union reg (Set.singleton idx)) (trTraceMapRegs trcol) regs
, trTraceMapCells =
foldr (\cell -> Map.insertWith Set.union cell (Set.singleton idx)) (trTraceMapCells trcol) cells
}
where
idx = length (trAllTraces trcol)

-- | Find all traces associated with the given list of registers and memory locations
-- (i.e. each trace is associated with at least one of the given locations).
-- Traces that are associated with multiple locations (i.e. added with 'insert') only
-- occur once in the result.
lookup ::
PSo.ValidSym sym =>
PA.ValidArch arch =>
[Some (MM.ArchReg arch)] ->
[Some (PMC.MemCell sym arch)] ->
TraceCollection sym arch ->
[CE.TraceEvents sym arch]
lookup regs cells trcol = let
reg_idxs = Set.unions $ map (\reg -> fromMaybe Set.empty $ Map.lookup reg (trTraceMapRegs trcol)) regs
cell_idxs = Set.unions $ map (\cell -> fromMaybe Set.empty $ Map.lookup cell (trTraceMapCells trcol)) cells
in map (\i -> (trAllTraces trcol) !! i) (Set.toList (Set.union reg_idxs cell_idxs))

{-
Not used a the moment, so left commented out to avoid cluttering the interface.
toList ::
forall sym arch.
TraceCollection sym arch ->
[(([Some (MM.ArchReg arch)], [Some (PMC.MemCell sym arch)]), CE.TraceEvents sym arch)]
toList trcol = map go [0..(length (trAllTraces trcol))]
where
go :: Int -> (([Some (MM.ArchReg arch)], [Some (PMC.MemCell sym arch)]), CE.TraceEvents sym arch)
go i = let
tr = trAllTraces trcol !! i
regs = Map.keys $ Map.filter (Set.member i) (trTraceMapRegs trcol)
cells = Map.keys $ Map.filter (Set.member i) (trTraceMapCells trcol)
in ((regs, cells), tr)
fromList ::
forall sym arch.
PSo.ValidSym sym =>
PA.ValidArch arch =>
[(([Some (MM.ArchReg arch)], [Some (PMC.MemCell sym arch)]), CE.TraceEvents sym arch)] ->
TraceCollection sym arch
fromList trs = foldr (\((regs, cells), tr) -> insert regs cells tr) empty trs
-}

instance (PA.ValidArch arch, PSo.ValidSym sym) => W4S.W4Serializable sym (TraceCollection sym arch) where
w4Serialize (TraceCollection allTraces regs cells) = do
W4S.object
[ "all_traces" W4S..= allTraces
, "trace_map_regs" W4S..= regs
, "trace_map_cells" W4S..= cells
]

instance forall sym arch. (PA.ValidArch arch, PSo.ValidSym sym) => IsTraceNode '(sym,arch) "trace_collection" where
type TraceNodeType '(sym,arch) "trace_collection" = TraceCollection sym arch
type TraceNodeLabel "trace_collection" = T.Text

prettyNode nm _tc = "Trace Collection: " <> PP.pretty nm
nodeTags = mkTags @'(sym,arch) @"trace_collection" [Summary, Simplified]
jsonNode sym lbl v = do
v_json <- W4S.w4ToJSON sym v
return $ JSON.object [ "name" JSON..= lbl , "trace_collection" JSON..= v_json ]
8 changes: 8 additions & 0 deletions src/Pate/Verification/AbstractDomain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ instance forall sym arch v. (PSo.ValidSym sym, PA.ValidArch arch) => W4S.W4Seria
eq_dom <- W4S.w4Serialize (absDomEq abs_dom)
return $ JSON.object [ "eq_domain" .= eq_dom, "val_domain" .= JSON.String "TODO" ]

instance forall sym arch. (PSo.ValidSym sym, PA.ValidArch arch) => W4S.W4SerializableF sym (AbstractDomain sym arch)


-- | Restrict an abstract domain to a single binary.
singletonDomain ::
PPa.PatchPairM m =>
Expand Down Expand Up @@ -888,6 +891,11 @@ ppDomainKind = \case
Postdomain -> "Intermediate postdomain"
ExternalPostDomain -> "Postdomain"

instance JSON.ToJSON DomainKind where
toJSON dk = JSON.toJSON (show dk)

instance W4S.W4Serializable sym DomainKind

instance (PA.ValidArch arch, PSo.ValidSym sym) => IsTraceNode '(sym,arch) "domain" where
type TraceNodeType '(sym,arch) "domain" = Some (AbstractDomain sym arch)
type TraceNodeLabel "domain" = DomainKind
Expand Down
Loading

0 comments on commit 6d0d70d

Please sign in to comment.