A software analysis framework built around the QBE intermediate language
git clone https://git.8pit.net/quebex.git
1-- SPDX-FileCopyrightText: 2024 University of Bremen 2-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net> 3-- 4-- SPDX-License-Identifier: MIT AND GPL-3.0-only 5 6module Language.QBE.Backend.Tracer 7 ( Branch (Branch), 8 newBranch, 9 fromBranch,10 ExecTrace,11 newExecTrace,12 toSExprs,13 appendBranch,14 appendCons,15 solveTrace,16 )17where1819import Control.Exception (throwIO)20import Language.QBE.Backend (SolverError (UnknownResult), prefixLength)21import Language.QBE.Backend.Model qualified as Model22import Language.QBE.Simulator.Symbolic.Expression qualified as SE23import SimpleSMT qualified as SMT2425-- Represents a branch condition in the executed code26data Branch27 = Branch28 Bool -- Whether negation of the branch was attempted29 SE.BitVector -- The symbolic branch condition30 deriving (Show, Eq)3132-- Create a new branch condition.33newBranch :: SE.BitVector -> Branch34newBranch = Branch False3536-- Create a new branch from an existing branch, thereby updating its metadata.37-- It is assumed that the condition, encoded in the branches, is equal.38fromBranch :: Branch -> Branch -> Branch39fromBranch (Branch wasNeg' _) (Branch wasNeg ast) =40 Branch (wasNeg || wasNeg') ast4142------------------------------------------------------------------------4344-- Represents a single execution through a program, tracking for each45-- symbolic branch condition if it was 'True' or 'False'.46type ExecTrace = [(Bool, Branch)]4748-- Create a new empty execution tree.49newExecTrace :: ExecTrace50newExecTrace = []5152-- Return all branch conditions of an 'ExecTrace'.53toSExprs :: ExecTrace -> [SMT.SExpr]54toSExprs = map (\(_, Branch _ bv) -> SE.toSExpr bv)5556-- Append a branch to the execution trace, denoting via a 'Bool'57-- if the branch was taken or if it was not taken.58appendBranch :: ExecTrace -> Bool -> Branch -> ExecTrace59appendBranch trace wasTrue branch = trace ++ [(wasTrue, branch)]6061-- Append a constraint to the execution tree. This constraint must62-- be true and, contrary to appendBranch, negation will not be63-- attempted for it.64appendCons :: ExecTrace -> SE.BitVector -> ExecTrace65appendCons trace cons = trace ++ [(True, Branch True cons)]6667-- For a given execution trace, return an assignment (represented68-- as a 'Model') which statisfies all symbolic branch conditions.69-- If such an assignment does not exist, then 'Nothing' is returned.70--71-- Throws a 'SolverError' on an unknown solver result (e.g., on timeout).72solveTrace :: SMT.Solver -> [SMT.SExpr] -> ExecTrace -> ExecTrace -> IO (Maybe Model.Model)73solveTrace solver inputVars oldTrace newTrace = do74 -- Determine the common prefix of the current trace and the old trace75 -- drop constraints beyond this common prefix from the current solver76 -- context. Thereby, keeping the common prefix and making use of77 -- incremental solving capabilities.78 let prefix = prefixLength newTrace oldTrace79 let toDrop = length oldTrace - prefix80 -- TODO: Don't pop if toDrop is zero.81 SMT.popMany solver $ fromIntegral toDrop8283 -- Only enforce new constraints, i.e. those beyond the common prefix.84 assertTrace (drop prefix newTrace)8586 isSat <- SMT.check solver87 case isSat of88 SMT.Sat -> Just <$> Model.getModel solver inputVars89 SMT.Unsat -> pure Nothing90 SMT.Unknown -> throwIO UnknownResult91 where92 -- Add all conditions enforced by the given 'ExecTrace' to the solver.93 -- Returns a list of all asserted conditions.94 assertTrace :: ExecTrace -> IO ()95 assertTrace [] = pure ()96 assertTrace t = do97 let conds = map (\(b, Branch _ c) -> SE.toCond b c) t98 mapM_ (\c -> SMT.push solver >> SMT.assert solver c) conds