quebex

A software analysis framework built around the QBE intermediate language

git clone https://git.8pit.net/quebex.git

  1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
  2--
  3-- SPDX-License-Identifier: GPL-3.0-only
  4
  5module Explorer (exploreTests) where
  6
  7import Data.List (sort)
  8import Data.Map qualified as Map
  9import Data.Maybe (fromJust)
 10import Language.QBE.Backend.Store qualified as ST
 11import Language.QBE.Backend.Tracer qualified as T
 12import Language.QBE.Simulator.Default.Expression qualified as DE
 13import Language.QBE.Types qualified as QBE
 14import Test.Tasty
 15import Test.Tasty.HUnit
 16import Util
 17
 18branchPoints :: [(ST.Assign, T.ExecTrace)] -> [[Bool]]
 19branchPoints lst = sort $ map (\(_, t) -> map fst t) lst
 20
 21findAssign :: [(ST.Assign, T.ExecTrace)] -> [Bool] -> Maybe ST.Assign
 22findAssign [] _ = Nothing
 23findAssign ((a, eTrace) : xs) toFind
 24  | map fst eTrace == toFind = Just a
 25  | otherwise = findAssign xs toFind
 26
 27------------------------------------------------------------------------
 28
 29exploreTests :: TestTree
 30exploreTests =
 31  testGroup
 32    "Tests for Symbolic Program Exploration"
 33    [ testCase "Explore' program with four execution paths" $
 34        do
 35          prog <-
 36            parseProg
 37              "function $branchOnInput(w %cond1, w %cond2) {\n\
 38              \@jump.1\n\
 39              \jnz %cond1, @branch.1, @branch.2\n\
 40              \@branch.1\n\
 41              \jmp @jump.2\n\
 42              \@branch.2\n\
 43              \jmp @jump.2\n\
 44              \@jump.2\n\
 45              \jnz %cond2, @branch.3, @branch.4\n\
 46              \@branch.3\n\
 47              \ret\n\
 48              \@branch.4\n\
 49              \ret\n\
 50              \}"
 51
 52          let funcDef = findFunc prog (QBE.GlobalIdent "branchOnInput")
 53          eTraces <- explore' prog funcDef [("cond1", QBE.Word), ("cond2", QBE.Word)]
 54
 55          let branches = branchPoints eTraces
 56          branches @?= [[False, False], [False, True], [True, False], [True, True]],
 57      testCase "Unsatisfiable branches" $
 58        do
 59          prog <-
 60            parseProg
 61              "function $branchOnInput(w %cond1) {\n\
 62              \@jump.1\n\
 63              \jnz %cond1, @branch.1, @branch.2\n\
 64              \@branch.1\n\
 65              \jmp @jump.2\n\
 66              \@branch.2\n\
 67              \jmp @jump.2\n\
 68              \@jump.2\n\
 69              \jnz %cond1, @branch.3, @branch.4\n\
 70              \@branch.3\n\
 71              \ret\n\
 72              \@branch.4\n\
 73              \ret\n\
 74              \}"
 75
 76          let funcDef = findFunc prog (QBE.GlobalIdent "branchOnInput")
 77          eTraces <- explore' prog funcDef [("cond1", QBE.Word)]
 78
 79          let branches = branchPoints eTraces
 80          branches @?= [[False, False], [True, True]],
 81      testCase "Branch with overflow arithmetics" $
 82        do
 83          prog <-
 84            parseProg
 85              "function $branchArithmetics(w %input) {\n\
 86              \@start\n\
 87              \%cond =w add %input, 1\n\
 88              \jnz %input, @branch.1, @branch.2\n\
 89              \@branch.1\n\
 90              \ret\n\
 91              \@branch.2\n\
 92              \ret\n\
 93              \}"
 94
 95          let funcDef = findFunc prog (QBE.GlobalIdent "branchArithmetics")
 96          eTraces <- explore' prog funcDef [("input", QBE.Word)]
 97
 98          let branches = branchPoints eTraces
 99          branches @?= [[False], [True]]
100
101          let assign = fromJust $ findAssign eTraces [False]
102          Map.lookup "input" assign @?= Just (DE.VWord 0),
103      testCase "Store symbolic value and memory, load it and pass it to function" $
104        do
105          prog <-
106            parseProg
107              "function $branchOnInput(w %cond1) {\n\
108              \@jump.1\n\
109              \jnz %cond1, @branch.1, @branch.2\n\
110              \@branch.1\n\
111              \jmp @jump.2\n\
112              \@branch.2\n\
113              \jmp @jump.2\n\
114              \@jump.2\n\
115              \jnz %cond1, @branch.3, @branch.4\n\
116              \@branch.3\n\
117              \ret\n\
118              \@branch.4\n\
119              \ret\n\
120              \}\n\
121              \function $entry(w %in) {\n\
122              \@start\n\
123              \%a =l alloc4 4\n\
124              \storew %in, %a\n\
125              \%l =w loadw %a\n\
126              \call $branchOnInput(w %l)\n\
127              \ret\n\
128              \}"
129
130          let funcDef = findFunc prog (QBE.GlobalIdent "entry")
131          eTraces <- explore' prog funcDef [("x", QBE.Word)]
132
133          let branches = branchPoints eTraces
134          branches @?= [[False, False], [True, True]],
135      testCase "Branch on a specific concrete 64-bit value" $
136        do
137          prog <-
138            parseProg
139              "function $f(l %input.0) {\n\
140              \@start\n\
141              \%input.1 =l sub %input.0, 42\n\
142              \jnz %input.1, @not42, @is42\n\
143              \@not42\n\
144              \ret\n\
145              \@is42\n\
146              \ret\n\
147              \}"
148
149          let funcDef = findFunc prog (QBE.GlobalIdent "f")
150          eTraces <- explore' prog funcDef [("y", QBE.Long)]
151
152          branchPoints eTraces @?= [[False], [True]]
153          let assign = fromJust $ findAssign eTraces [False]
154          Map.lookup "y" assign @?= Just (DE.VLong 42),
155      testCase "Branching with subtyping" $
156        do
157          prog <-
158            parseProg
159              "function $f(l %input.0, w %input.1) {\n\
160              \@start\n\
161              \%added =w add %input.1, 1\n\
162              \%subed =w sub %added, %input.1\n\
163              \%result =w add %added, %subed\n\
164              \jnz %result, @b1, @b2\n\
165              \@b1\n\
166              \jnz %input.0, @b2, @b2\n\
167              \@b2\n\
168              \ret\n\
169              \}"
170
171          let funcDef = findFunc prog (QBE.GlobalIdent "f")
172          eTraces <- explore' prog funcDef [("y", QBE.Long), ("x", QBE.Word)]
173
174          branchPoints eTraces @?= [[False], [True, False], [True, True]]
175    ]