Skip to content

Commit b45683f

Browse files
katrinafyiailrst
andauthored
track PC and assert at beginning of blocks (#387)
* feat: maintain program counter and assert at beginning of blocks this makes a fairly significant change to the GTIRB loader by inserting PC increment statements after instructions and checking the PC at the entry of blocks. this is done by changing the loader to maintain PC and branchtaken assignments, and inserting a default increment if there is no branch. * apportion TempIf statements into successor blocks, then remove TempIf this fixes the problemetic cross pattern we saw, where there were extra blocks inserted before the original successor blocks. * add RemoveUnreachableBlocks pass to clean up padding blocks * move RemovePCStatements into load() this fixes the DifferentialAnalysisTest seeing program counters in its loaded IR, but it is maybe undesirable to do a transformation in the load() method. regardless, the current approach (of keep PC, then remove if unwanted) seems to necessitate this, as later parts of the runutils pipeline make assumptions that PC wont be present and fail in unexpected ways otherwise. * add back RemoveUnreachableBlocks to loadAndTranslate * update expected. some differences in memory sizes and function inlining? * change --pc argument to an enum * implement Assert feature, use requires/ensures instead of assume * use "pc-tracking" label * scalafmt * add PCTrackingTest, using a test case which has a runtime-dependent branch * inject requires/ensures through Specification logic to hopefully make sure it's picked up by the parameter form transform. despite this, it looks like it still is not working. --------- Co-authored-by: am <[email protected]>
1 parent 32e2635 commit b45683f

File tree

55 files changed

+2770
-3230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2770
-3230
lines changed

src/main/scala/Main.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ object Main {
153153
generateRelyGuarantees: Flag,
154154
@arg(name = "simplify", doc = "Partial evaluate / simplify BASIL IR before output (implies --parameter-form)")
155155
simplify: Flag,
156+
@arg(
157+
name = "pc",
158+
doc = "Program counter mode, supports GTIRB only. (options: none | keep | assert) (default: none)"
159+
)
160+
pcTracking: Option[String],
156161
@arg(
157162
name = "validate-simplify",
158163
doc = "Emit SMT2 check for validation of simplification expression rewrites 'rewrites.smt2'"
@@ -253,7 +258,8 @@ object Main {
253258
case Some("prereq") => Some(Prereq)
254259
case Some("checks") => Some(Checks)
255260
case Some("standard") => Some(Standard)
256-
case None => Some(Standard)
261+
case Some("none") => None
262+
case None => None
257263
case Some(_) =>
258264
throw new IllegalArgumentException("Illegal option to dsa, allowed are: (prereq|standard|checks)")
259265
} else {
@@ -289,7 +295,8 @@ object Main {
289295
mainProcedureName = conf.mainProcedureName,
290296
procedureTrimDepth = conf.procedureDepth,
291297
parameterForm = conf.parameterForm.value,
292-
trimEarly = conf.trimEarly.value
298+
trimEarly = conf.trimEarly.value,
299+
pcTracking = PCTrackingOption.valueOf(conf.pcTracking.getOrElse("none").capitalize)
293300
),
294301
runInterpret = conf.interpret.value,
295302
simplify = conf.simplify.value,
@@ -308,18 +315,11 @@ object Main {
308315
assert(result.boogie.nonEmpty)
309316
var failed = false
310317
for (b <- result.boogie) {
311-
val fname = b.filename
312-
val timer = PerformanceTimer("Verify", LogLevel.INFO)
313-
val cmd = Seq("boogie", "/useArrayAxioms", fname)
314-
Logger.info(s"Running: ${cmd.mkString(" ")}")
315-
val output = cmd.!!
316-
val result = util.boogie_interaction.parseOutput(output)
318+
val result = b.verifyBoogie(b.filename)
317319
result.kind match {
318320
case BoogieResultKind.Verified(c, _) if c > 0 => ()
319321
case _ => failed = true
320322
}
321-
Logger.info(result.toString)
322-
timer.checkPoint("Finish")
323323
}
324324
if (failed) {
325325
throw Exception("Verification failed")

src/main/scala/boogie/BProgram.scala

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
package boogie
2-
import java.io.{StringWriter, Writer}
2+
3+
import util.{PerformanceTimer, Logger, LogLevel}
4+
5+
import scala.sys.process.*
6+
import scala.collection.immutable.Seq
7+
8+
import java.io.{StringWriter, Writer, BufferedWriter, FileWriter}
9+
import java.nio.file.{Files, Paths}
310

411
case class BProgram(declarations: List[BDeclaration], filename: String) {
512
override def toString: String = declarations.flatMap(x => x.toBoogie).mkString(System.lineSeparator())
613

714
def writeToString(w: Writer): Unit = {
815
declarations.foreach(x => x.writeToString(w))
916
}
17+
18+
/**
19+
* Invokes Boogie to verify the current BProgram.
20+
*/
21+
def verifyBoogie(fname: String = "") = {
22+
val temp =
23+
if !fname.isEmpty then Paths.get(fname)
24+
else Files.createTempFile("basil-boogie-temp", ".bpl")
25+
26+
val wr = BufferedWriter(FileWriter(temp.toFile))
27+
try {
28+
writeToString(wr)
29+
} finally {
30+
wr.close()
31+
}
32+
33+
val timer = PerformanceTimer("Verify", LogLevel.INFO)
34+
val cmd = Seq("boogie", "/useArrayAxioms", temp.toString)
35+
Logger.info(s"Running: ${cmd.mkString(" ")}")
36+
37+
val output = cmd.!!
38+
val result = util.boogie_interaction.parseOutput(output)
39+
40+
Logger.info(result.toString)
41+
timer.checkPoint("Finish")
42+
43+
result
44+
}
1045
}
1146

1247
trait BDeclaration extends HasAttributes {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package ir.transforms
2+
3+
import util.{Logger, PCTrackingOption}
4+
import ir.*
5+
6+
object PCTracking {
7+
8+
def applyPCTracking(mode: PCTrackingOption, program: Program): Unit = {
9+
10+
// convenience variable to hold all procedures with defined program counters.
11+
val proceduresWithPCs = program.procedures.collect {
12+
case p if p.address.isDefined => (p, p.address.get)
13+
}
14+
15+
mode match {
16+
case PCTrackingOption.None =>
17+
program.collect {
18+
case x: Statement if x.label == Some("pc-tracking") =>
19+
x.parent.statements.remove(x)
20+
21+
// note: direct jumps are from aslp and so do not have the pc-tracking tag
22+
case x @ LocalAssign(Register("_PC", 64), _, _) =>
23+
x.parent.statements.remove(x)
24+
}
25+
Logger.info(s"[!] Removed all PC-related statements")
26+
27+
case PCTrackingOption.Keep =>
28+
Logger.info(s"[!] Removing PC-tracking assertion statements, keeping PC assignments")
29+
program.collect { case x @ Assert(_, _, Some("pc-tracking")) =>
30+
x.parent.statements.remove(x)
31+
}
32+
case PCTrackingOption.Assert =>
33+
Logger.info(s"[!] Inserting PC-tracking requires/ensures")
34+
35+
// extra requires/ensures clauses for maintaining PC
36+
proceduresWithPCs.foreach((proc, addr) => {
37+
val pcVar = Register("_PC", 64)
38+
val r30Var = Register("R30", 64)
39+
val addrVar = BitVecLiteral(addr, 64)
40+
41+
val pcRequires = BinaryExpr(ir.EQ, pcVar, addrVar)
42+
val pcEnsures = BinaryExpr(ir.EQ, pcVar, OldExpr(r30Var))
43+
44+
proc.requiresExpr = pcRequires +: proc.requiresExpr
45+
proc.ensuresExpr = pcEnsures +: proc.ensuresExpr
46+
})
47+
}
48+
}
49+
50+
}

src/main/scala/ir/transforms/ProcedureParameters.scala

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ def liftProcedureCallAbstraction(ctx: util.IRContext): util.IRContext = {
160160

161161
while (removeDeadInParams(ctx.program)) {}
162162

163-
ctx.copy(specification = specToProcForm(ctx.specification, formalParams.mappingInparam, formalParams.mappingOutparam))
163+
ctx.program.procedures.foreach(SpecFixer.updateInlineSpec(formalParams.mappingInparam, formalParams.mappingOutparam))
164+
ctx.copy(specification =
165+
SpecFixer.specToProcForm(ctx.specification, formalParams.mappingInparam, formalParams.mappingOutparam)
166+
)
164167
}
165168

166169
def clearParams(p: Program) = {
@@ -395,22 +398,24 @@ def inOutParams(
395398

396399
val alwaysReturnParams = (0 to 7).map(i => Register(s"R$i", 64))
397400

401+
val pc = Register("_PC", 64)
402+
398403
val inout = readWrites.collect {
399404
case (proc, rws) if p.mainProcedure == proc => {
400405
// no callers of main procedure so keep the whole read/write set
401406
// of registers
402407

403408
val outParams = (overapprox.intersect(DefinedOnAllPaths.proc(proc)))
404409
val inParams = lives(proc)._1
405-
proc -> (inParams, outParams)
410+
proc -> (inParams + pc, outParams + pc)
406411
}
407412
case (proc, rws) => {
408413
val liveStart = lives(proc)._1
409414
val liveEnd = lives(proc)._2 ++ alwaysReturnParams
410415

411416
val outParams = liveEnd.intersect(rws.writes)
412417
val inParams = liveStart
413-
proc -> (inParams, outParams)
418+
proc -> (inParams + pc, outParams + pc)
414419
}
415420
}.toMap
416421

@@ -540,18 +545,51 @@ class SetActualParams(
540545

541546
}
542547

543-
def specToProcForm(
544-
spec: Specification,
545-
mappingInparam: Map[Procedure, Map[LocalVar, Variable]],
546-
mappingOutparam: Map[Procedure, Map[LocalVar, Variable]]
547-
): Specification = {
548+
object SpecFixer {
548549
import boogie.*
549550

550-
def toNameMapping(v: Map[LocalVar, Variable]): Map[String, String] = {
551-
v.map(v => (v._2.name, v._1.name)) ++ v.map(v => ("Gamma_" + v._2.name, "Gamma_" + v._1.name))
551+
class ExprToOld(varInPre: Map[String, Variable], varInPost: Map[String, Variable], initIsPost: Boolean = false)
552+
extends CILVisitor {
553+
554+
var isPost = initIsPost
555+
556+
override def vexpr(e: Expr) = e match {
557+
case OldExpr(inner) if !isPost => {
558+
throw Exception("nested Old or Old in single-state context")
559+
}
560+
case OldExpr(inner) if isPost => {
561+
isPost = false
562+
ChangeDoChildrenPost(
563+
inner,
564+
e => {
565+
isPost = true
566+
e
567+
}
568+
)
569+
}
570+
case _ => DoChildren()
571+
}
572+
573+
def changeVar(v: Variable) = {
574+
val repl = v match {
575+
case l: Variable if isPost && varInPost.contains(l.name) => varInPost(l.name)
576+
case l: Variable if varInPre.contains(l.name) => varInPre(l.name)
577+
case o => o
578+
}
579+
ChangeTo(repl)
580+
}
581+
582+
override def vlvar(v: Variable) = changeVar(v)
583+
override def vrvar(v: Variable) = changeVar(v)
584+
585+
}
586+
587+
def convVarToOldExpr(varInPre: Map[String, Variable], varInPost: Map[String, Variable], isPost: Boolean = false)(
588+
b: Expr
589+
): Expr = {
590+
val visitor = ExprToOld(varInPre, varInPost, isPost)
591+
visit_expr(visitor, b)
552592
}
553-
val varToInVar: Map[String, Map[String, String]] = mappingInparam.map(p => (p._1.procName -> toNameMapping(p._2)))
554-
val varToOutVar: Map[String, Map[String, String]] = mappingOutparam.map(p => (p._1.procName -> toNameMapping(p._2)))
555593

556594
def convVarToOld(varInPre: Map[String, String], varInPost: Map[String, String], isPost: Boolean = false)(
557595
b: BExpr
@@ -560,7 +598,7 @@ def specToProcForm(
560598
b match {
561599
case b: BVariable if isPost && varInPost.contains(b.name) => BVariable(varInPost(b.name), b.getType, b.scope)
562600
case b: BVariable if !isPost && varInPre.contains(b.name) => BVariable(varInPre(b.name), b.getType, b.scope)
563-
case b: BVar => b
601+
case b: BVariable if !isPost => b
564602
// case b : _ => varToOld(b)
565603
case b: BLiteral => b
566604
case b: BVExtract => b.copy(body = varToOld(b.body))
@@ -590,20 +628,61 @@ def specToProcForm(
590628
case b: GammaStore => b.copy(index = varToOld(b.index), value = varToOld(b.value))
591629
case b: L => b.copy(index = varToOld(b.index))
592630
case b: SpecVar => b
631+
case b: BVar =>
632+
???
593633
}
594634
}
595635

596-
val ns = spec.copy(subroutines = spec.subroutines.map(s => {
597-
if (s.requires.nonEmpty || s.ensures.nonEmpty) {
598-
val in = varToInVar(s.name)
599-
val out = varToOutVar(s.name)
600-
s.copy(
601-
requires = s.requires.map(convVarToOld(in, out, false)),
602-
ensures = s.ensures.map(convVarToOld(in, out, true))
603-
)
604-
} else {
605-
s
636+
def updateInlineSpec(
637+
mappingInparam: Map[Procedure, Map[LocalVar, Variable]],
638+
mappingOutparam: Map[Procedure, Map[LocalVar, Variable]]
639+
)(p: Procedure) = {
640+
641+
def toNameMapping(v: Map[LocalVar, Variable]): Map[String, String] = {
642+
v.map(v => (v._2.name, v._1.name)) ++ v.map(v => ("Gamma_" + v._2.name, "Gamma_" + v._1.name))
606643
}
607-
}))
608-
ns
644+
645+
val varToInVar = mappingInparam.map(p => (p._1 -> toNameMapping(p._2)))
646+
val varToOutVar = mappingOutparam.map(p => (p._1 -> toNameMapping(p._2)))
647+
648+
p.requires = p.requires.map(convVarToOld(varToInVar(p), varToOutVar(p), false))
649+
p.ensures = p.ensures.map(convVarToOld(varToInVar(p), varToOutVar(p), true))
650+
651+
def toVarMapping(v: Map[LocalVar, Variable]): Map[String, Variable] = {
652+
v.map(v => (v._2.name, v._1))
653+
}
654+
655+
p.requiresExpr =
656+
p.requiresExpr.map(convVarToOldExpr(toVarMapping(mappingInparam(p)), toVarMapping(mappingOutparam(p)), false))
657+
p.ensuresExpr =
658+
p.ensuresExpr.map(convVarToOldExpr(toVarMapping(mappingInparam(p)), toVarMapping(mappingOutparam(p)), true))
659+
660+
}
661+
662+
def specToProcForm(
663+
spec: Specification,
664+
mappingInparam: Map[Procedure, Map[LocalVar, Variable]],
665+
mappingOutparam: Map[Procedure, Map[LocalVar, Variable]]
666+
): Specification = {
667+
668+
def toNameMapping(v: Map[LocalVar, Variable]): Map[String, String] = {
669+
v.map(v => (v._2.name, v._1.name)) ++ v.map(v => ("Gamma_" + v._2.name, "Gamma_" + v._1.name))
670+
}
671+
val varToInVar: Map[String, Map[String, String]] = mappingInparam.map(p => (p._1.procName -> toNameMapping(p._2)))
672+
val varToOutVar: Map[String, Map[String, String]] = mappingOutparam.map(p => (p._1.procName -> toNameMapping(p._2)))
673+
674+
val ns = spec.copy(subroutines = spec.subroutines.map(s => {
675+
if (s.requires.nonEmpty || s.ensures.nonEmpty) {
676+
val in = varToInVar(s.name)
677+
val out = varToOutVar(s.name)
678+
s.copy(
679+
requires = s.requires.map(convVarToOld(in, out, false)),
680+
ensures = s.ensures.map(convVarToOld(in, out, true))
681+
)
682+
} else {
683+
s
684+
}
685+
}))
686+
ns
687+
}
609688
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ir.transforms
2+
3+
import ir.*
4+
import ir.cilvisitor.*
5+
6+
class RemovePCStatements extends CILVisitor {
7+
override def vstmt(s: Statement) = {
8+
val vars = allVarsPos(s)
9+
if (vars.contains(Register("_PC", 64))) {
10+
ChangeTo(List())
11+
} else {
12+
SkipChildren()
13+
}
14+
}
15+
}

0 commit comments

Comments
 (0)