├── .github └── workflows │ └── scala.yml ├── .gitignore ├── .scalafmt.conf ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties ├── sbt-antlr4.sbt └── sbt-scalafmt.sbt ├── sbt ├── src ├── main │ ├── antlr4 │ │ └── Java.g4 │ └── scala │ │ └── com │ │ └── simplytyped │ │ └── yoyak │ │ ├── Global.scala │ │ ├── Main.scala │ │ ├── OptionParser.scala │ │ ├── Options.scala │ │ ├── analysis │ │ ├── IntervalAnalysis.scala │ │ └── StringAnalysis.scala │ │ ├── android │ │ └── AndroidAPIs.scala │ │ ├── framework │ │ ├── BackwardAnalysis.scala │ │ ├── ForwardAnalysis.scala │ │ ├── algo │ │ │ ├── CfgNavigator.scala │ │ │ ├── DoWidening.scala │ │ │ ├── FlowInsensitiveFixedPointComputation.scala │ │ │ ├── FlowInsensitiveIteration.scala │ │ │ ├── FlowSensitiveFixedPointComputation.scala │ │ │ ├── FlowSensitiveIteration.scala │ │ │ ├── InterproceduralIteration.scala │ │ │ ├── WideningAtLoopHeads.scala │ │ │ └── Worklist.scala │ │ ├── domain │ │ │ ├── ArithmeticOps.scala │ │ │ ├── Galois.scala │ │ │ ├── LatticeOps.scala │ │ │ ├── LatticeWithTopOps.scala │ │ │ ├── MapDom.scala │ │ │ ├── arith │ │ │ │ ├── Interval.scala │ │ │ │ └── IntervalInt.scala │ │ │ └── mem │ │ │ │ ├── ArrayJoinModel.scala │ │ │ │ ├── Localizable.scala │ │ │ │ ├── MemDom.scala │ │ │ │ ├── MemDomLike.scala │ │ │ │ ├── MemElems.scala │ │ │ │ ├── Resolvable.scala │ │ │ │ ├── SimpleLocalizer.scala │ │ │ │ ├── SimpleResolver.scala │ │ │ │ └── StdObjectModel.scala │ │ └── semantics │ │ │ ├── AbstractTransferable.scala │ │ │ ├── Narrowing.scala │ │ │ ├── StdSemantics.scala │ │ │ └── Widening.scala │ │ ├── graph │ │ ├── EdgeLike.scala │ │ ├── GraphLike.scala │ │ ├── ImmutableGraphLike.scala │ │ ├── NodeLike.scala │ │ └── algo │ │ │ ├── GraphRefactoring.scala │ │ │ ├── GraphRefactoringImpl.scala │ │ │ ├── GraphTraverse.scala │ │ │ └── GraphTraverseImpl.scala │ │ ├── il │ │ ├── Attachable.scala │ │ ├── CommonIL.scala │ │ ├── CommonILHelper.scala │ │ ├── PrettyPrinter.scala │ │ ├── SourceInfo.scala │ │ ├── Typable.scala │ │ ├── cfg │ │ │ ├── BasicBlock.scala │ │ │ ├── BasicEdge.scala │ │ │ ├── CFG.scala │ │ │ └── CommonILToCFG.scala │ │ ├── hierarchy │ │ │ ├── ClassHierarchy.scala │ │ │ └── ClassHierarchyBuilder.scala │ │ └── opt │ │ │ ├── VarSplitting.scala │ │ │ └── analysis │ │ │ └── DefReachability.scala │ │ ├── parser │ │ ├── cil │ │ │ ├── CommonILParser.scala │ │ │ └── CommonILTransform.scala │ │ ├── dex │ │ │ ├── DexlibDexParser.scala │ │ │ └── DexlibDexTransformer.scala │ │ └── java │ │ │ ├── AntlrJavaParser.scala │ │ │ └── AntlrJavaTransformer.scala │ │ ├── phases │ │ ├── CfgGenPhase.scala │ │ ├── ClassHierarchyGenPhase.scala │ │ ├── DexParserPhase.scala │ │ ├── Phase.scala │ │ ├── PhaseDriver.scala │ │ ├── StringAnalysisPhase.scala │ │ └── VarSplittingPhase.scala │ │ ├── solver │ │ ├── algo │ │ │ ├── BCP.scala │ │ │ ├── CDCL.scala │ │ │ └── QCP.scala │ │ └── domain │ │ │ ├── AbsDomLike.scala │ │ │ ├── CNF.scala │ │ │ ├── CNFConversions.scala │ │ │ ├── DIMACSParser.scala │ │ │ ├── MapDom.scala │ │ │ └── PAssignDom.scala │ │ └── util │ │ ├── Log.scala │ │ └── Option.scala └── test │ └── scala │ └── com │ └── simplytyped │ └── yoyak │ ├── analysis │ └── IntervalAnalysisTest.scala │ ├── framework │ └── domain │ │ ├── MapDomTest.scala │ │ ├── arith │ │ └── IntervalTest.scala │ │ └── mem │ │ └── MemDomTest.scala │ ├── graph │ ├── GraphGenerator.scala │ ├── ImmutableGraphLikeSpecTest.scala │ ├── ImmutableGraphLikeTest.scala │ ├── IntegerGraphParser.scala │ └── algo │ │ ├── GraphRefactoringTest.scala │ │ └── GraphTraverseTest.scala │ ├── parser │ ├── cil │ │ └── CommonILParserTest.scala │ ├── dex │ │ └── DexlibDexTransformerTest.scala │ └── java │ │ ├── AntlrJavaParserTest.scala │ │ ├── AntlrJavaTransformerTest.scala │ │ └── AntlrTestHelper.scala │ └── solver │ ├── algo │ ├── BCPTest.scala │ ├── CDCLTest.scala │ └── QCPTest.scala │ └── domain │ └── DIMACSParserTest.scala └── test └── apk ├── pp2.apk ├── sample-app.apk └── sin.apk /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Scala CI 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up JDK 8 17 | uses: actions/setup-java@v2 18 | with: 19 | java-version: '8' 20 | distribution: 'adopt' 21 | - name: Run tests 22 | run: sbt test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | .idea_modules/ 4 | *.iml 5 | .bsp/ 6 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "2.7.5" 2 | maxColumn = 80 3 | align.preset = more 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yoyak 2 | 3 | [![Build Status](https://github.com/ihji/yoyak/actions/workflows/scala.yml/badge.svg)](https://github.com/ihji/yoyak/actions/workflows/scala.yml) 4 | 5 | Generic program analysis engine based on the theory of abstract interpretation. 6 | This project is at the *very* early stage (i.e. not usable for any purpose except its own development). 7 | 8 | For those of you who want to know more about yoyak, this is the [presentation slide](http://www.slideshare.net/ihji/yoyak-scaladays-2015) at ScalaDays 2015. 9 | 10 | ## License 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "yoyak" 2 | 3 | version := "0.1-SNAPSHOT" 4 | 5 | scalaVersion := "2.13.7" 6 | 7 | libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value 8 | 9 | libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.0" 10 | 11 | libraryDependencies += "org.typelevel" %% "cats-core" % "2.7.0" 12 | 13 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.10" % "test" 14 | 15 | libraryDependencies += "org.scalatestplus" %% "scalacheck-1-15" % "3.2.10.0" % "test" 16 | 17 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.15.4" % "test" 18 | 19 | libraryDependencies += "org.smali" % "dexlib2" % "2.0.3" 20 | 21 | libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.15.0" 22 | 23 | libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.15.0" 24 | 25 | libraryDependencies += "commons-cli" % "commons-cli" % "1.5.0" 26 | 27 | enablePlugins(Antlr4Plugin) 28 | 29 | Antlr4 / antlr4PackageName := Some("com.simplytyped.yoyak.parser") 30 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.6 2 | -------------------------------------------------------------------------------- /project/sbt-antlr4.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.3") 2 | -------------------------------------------------------------------------------- /project/sbt-scalafmt.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") 2 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/Global.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Program 4 | import com.simplytyped.yoyak.il.hierarchy.ClassHierarchy 5 | 6 | /* global state */ 7 | case class Global( 8 | pgm: Option[Program] = None, 9 | classHierarchy: Option[ClassHierarchy] = None 10 | ) 11 | 12 | object Global { 13 | val empty = Global(None, None) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/Main.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak 2 | 3 | import com.simplytyped.yoyak.phases._ 4 | 5 | object Main { 6 | val dexParser = new DexParserPhase 7 | val classHierarchy = new ClassHierarchyGenPhase 8 | val cfgGen = new CfgGenPhase 9 | val varSplitting = new VarSplittingPhase 10 | val stringAnalysis = new StringAnalysisPhase 11 | 12 | val androidStringAnalysis = { 13 | val phases = 14 | List(dexParser, classHierarchy, cfgGen, varSplitting, stringAnalysis) 15 | phases.sliding(2).foreach { list => list(1).dependsOn(list(0)) } 16 | phases.head 17 | } 18 | 19 | def main(args: Array[String]) { 20 | new OptionParser().parse(args) 21 | val driver = new PhaseDriver(androidStringAnalysis) 22 | val global = Global.empty 23 | driver.run(global) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/OptionParser.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak 2 | 3 | import org.apache.commons.cli.{ 4 | Options => Opts, 5 | HelpFormatter, 6 | CommandLine, 7 | PosixParser, 8 | OptionBuilder 9 | } 10 | 11 | import scala.collection.mutable.ListBuffer 12 | 13 | class OptionParser { 14 | val (opts, lineBuilder): (Opts, List[(Options, CommandLine) => Unit]) = { 15 | val optsBuffer = new Opts 16 | val lineBuildBuffer = new ListBuffer[(Options, CommandLine) => Unit] 17 | 18 | /* add options here : START */ 19 | OptionBuilder.withDescription("print help messages") 20 | optsBuffer.addOption(OptionBuilder.create("help")) 21 | lineBuildBuffer.append((o, c) => 22 | if (c.hasOption("help")) { printUsage(); System.exit(0) } 23 | ) 24 | 25 | OptionBuilder.withDescription("target apk to analyze") 26 | OptionBuilder.hasArg() 27 | OptionBuilder.withArgName("file") 28 | optsBuffer.addOption(OptionBuilder.create("target_apk")) 29 | lineBuildBuffer.append((o, c) => 30 | Option(c.getOptionValue("target_apk")).foreach { x => 31 | o.target_apk = Some(x) 32 | } 33 | ) 34 | /* add options here : END */ 35 | 36 | (optsBuffer, lineBuildBuffer.toList) 37 | } 38 | def generateOptions(cmdline: CommandLine): Options = { 39 | val options = new Options 40 | lineBuilder.foreach { _(options, cmdline) } 41 | options 42 | } 43 | def printUsage() { 44 | val formatter = new HelpFormatter 45 | formatter.printHelp("yoyak", opts) 46 | } 47 | def parse(args: Array[String]) { 48 | val parser = new PosixParser 49 | val cmdline = parser.parse(opts, args) 50 | val options = generateOptions(cmdline) 51 | Options.g = options 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/Options.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak 2 | 3 | class Options { 4 | var target_apk: Option[String] = None 5 | } 6 | 7 | object Options { 8 | var g = new Options 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/analysis/IntervalAnalysis.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.analysis 2 | 3 | import com.simplytyped.yoyak.analysis.IntervalAnalysis.GMemory 4 | import com.simplytyped.yoyak.framework.ForwardAnalysis.FlowSensitiveForwardAnalysis 5 | import com.simplytyped.yoyak.framework.domain.Galois.{ 6 | SetAbstraction, 7 | GaloisIdentity 8 | } 9 | import com.simplytyped.yoyak.framework.domain.{ArithmeticOps, LatticeOps} 10 | import com.simplytyped.yoyak.framework.domain.arith.IntervalInt 11 | import com.simplytyped.yoyak.framework.domain.mem.MemDom 12 | import com.simplytyped.yoyak.framework.semantics.{ 13 | Widening, 14 | StdSemantics, 15 | AbstractTransferable 16 | } 17 | import com.simplytyped.yoyak.il.cfg.CFG 18 | 19 | class IntervalAnalysis(cfg: CFG) { 20 | def run() = { 21 | import IntervalAnalysis.{memDomOps, absTransfer, widening} 22 | val analysis = new FlowSensitiveForwardAnalysis[GMemory](cfg) 23 | val output = analysis.compute 24 | output 25 | } 26 | } 27 | 28 | object IntervalAnalysis { 29 | type Memory = MemDom[IntervalInt, SetAbstraction[Any]] 30 | type GMemory = GaloisIdentity[Memory] 31 | implicit val absTransfer: AbstractTransferable[GMemory] = 32 | new StdSemantics[IntervalInt, SetAbstraction[Any], Memory] { 33 | val arithOps: ArithmeticOps[IntervalInt] = IntervalInt.arithOps 34 | } 35 | 36 | implicit val memDomOps: LatticeOps[GMemory] = 37 | MemDom.ops[IntervalInt, SetAbstraction[Any]] 38 | implicit val widening: Option[Widening[GMemory]] = { 39 | implicit val NoWideningForSetAbstraction = 40 | Widening.NoWidening[SetAbstraction[Any]] 41 | Some(MemDom.widening[IntervalInt, SetAbstraction[Any]]) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/analysis/StringAnalysis.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.analysis 2 | 3 | import com.simplytyped.yoyak.analysis.StringAnalysis.SetInt 4 | import com.simplytyped.yoyak.android.AndroidAPIs 5 | import com.simplytyped.yoyak.framework.ForwardAnalysis.FlowSensitiveForwardAnalysis 6 | import com.simplytyped.yoyak.framework.domain.Galois.{ 7 | SetAbstraction, 8 | GaloisIdentity 9 | } 10 | import com.simplytyped.yoyak.framework.domain.mem.MemElems.{AbsTop, AbsBox} 11 | import com.simplytyped.yoyak.framework.domain._ 12 | import com.simplytyped.yoyak.framework.domain.mem.MemDom 13 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable.Context 14 | import com.simplytyped.yoyak.framework.semantics.StdSemantics 15 | import com.simplytyped.yoyak.il.CommonIL.Statement.{Stmt, Invoke} 16 | import com.simplytyped.yoyak.il.CommonIL.Value 17 | import com.simplytyped.yoyak.il.CommonIL.Value.{Loc, StringConstant, Constant} 18 | import com.simplytyped.yoyak.il.cfg.{BasicBlock, CFG} 19 | 20 | class StringAnalysis(cfg: CFG) { 21 | def run(): List[String] = { 22 | import StringAnalysis.{absTransfer, memDomOps} 23 | val analysis = new FlowSensitiveForwardAnalysis[ 24 | GaloisIdentity[MemDom[SetInt, SetAbstraction[String]]] 25 | ](cfg) 26 | val output = analysis.compute 27 | check(output) 28 | } 29 | def findHost( 30 | stmt: Stmt, 31 | mem: MemDom[SetInt, SetAbstraction[String]] 32 | ): List[String] = { 33 | stmt match { 34 | case Invoke(_, callee) if AndroidAPIs.internet(callee.callee) => 35 | callee.args.foldLeft(List.empty[String]) { case (list, v) => 36 | v match { 37 | case l: Loc => 38 | mem.get(l) match { 39 | case AbsBox(strs) => list ++ strs 40 | case _ => list 41 | } 42 | case _ => list 43 | } 44 | } 45 | case _ => List.empty[String] 46 | } 47 | } 48 | def check( 49 | output: MapDom[BasicBlock, GaloisIdentity[ 50 | MemDom[SetInt, SetAbstraction[String]] 51 | ]] 52 | ): List[String] = { 53 | cfg.nodes.foldLeft(List.empty[String]) { (list, node) => 54 | val prevs = cfg.getPrevs(node) 55 | val input = prevs.foldLeft(StringAnalysis.memDomOps.bottom) { (m, p) => 56 | StringAnalysis.memDomOps.\/(m, output.get(p)) 57 | } 58 | val hosts = node.data.getStmts 59 | .foldLeft(input, List.empty[String]) { case ((m, l), stmt) => 60 | val hosts = findHost(stmt, m) 61 | val next = StringAnalysis.absTransfer.transfer(m, stmt) 62 | (next, l ++ hosts) 63 | } 64 | ._2 65 | list ++ hosts 66 | } 67 | } 68 | } 69 | 70 | object StringAnalysis { 71 | class SetInt extends Galois { 72 | type Conc = Int 73 | type Abst = Set[Int] 74 | } 75 | 76 | implicit val absTransfer: StdSemantics[SetInt, SetAbstraction[String], MemDom[ 77 | SetInt, 78 | SetAbstraction[String] 79 | ]] = new StdSemantics[ 80 | SetInt, 81 | SetAbstraction[String], 82 | MemDom[SetInt, SetAbstraction[String]] 83 | ] { 84 | val arithOps = StringAnalysis.arithOps 85 | override protected def evalConstant( 86 | v: Value.Constant, 87 | input: MemDom[SetInt, SetAbstraction[String]] 88 | )(implicit context: Context) = { 89 | v match { 90 | case StringConstant(s) => 91 | (AbsBox[SetAbstraction[String]](Set(s)), input) 92 | case _ => (AbsTop, input) 93 | } 94 | } 95 | } 96 | 97 | implicit val arithOps: ArithmeticOps[SetInt] = new ArithmeticOps[SetInt] { 98 | override def +(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 99 | 100 | override def /(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 101 | 102 | override def -(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 103 | 104 | override def *(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 105 | 106 | override def lift(const: Constant): Set[Int] = ??? 107 | 108 | override def unlift(abs: Set[Int]): Option[Set[Int]] = ??? 109 | 110 | override def isTop(v: Set[Int]): Boolean = false 111 | 112 | override def bottom: Set[Int] = Set.empty[Int] 113 | 114 | override def \/(lhs: Set[Int], rhs: Set[Int]): Set[Int] = lhs ++ rhs 115 | 116 | override def partialCompare(lhs: Set[Int], rhs: Set[Int]): Double = 117 | if (lhs == rhs) 0.0 118 | else if (lhs subsetOf rhs) -1.0 119 | else if (rhs subsetOf lhs) 1.0 120 | else Double.NaN 121 | } 122 | implicit val memDomOps 123 | : LatticeOps[GaloisIdentity[MemDom[SetInt, SetAbstraction[String]]]] = 124 | MemDom.ops[SetInt, SetAbstraction[String]] 125 | } 126 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/android/AndroidAPIs.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.android 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Type.{IntegerType, CommonTypes} 4 | import com.simplytyped.yoyak.il.CommonIL.{ClassName, MethodSig} 5 | 6 | object AndroidAPIs { 7 | val internet = Set( 8 | MethodSig( 9 | ClassName("java.net.Socket"), 10 | "", 11 | List(CommonTypes.String, IntegerType) 12 | ), 13 | MethodSig(ClassName("java.net.URL"), "", List(CommonTypes.String)) 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/BackwardAnalysis.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework 2 | 3 | import com.simplytyped.yoyak.framework.algo.{ 4 | WideningAtLoopHeads, 5 | FlowSensitiveFixedPointComputation, 6 | FlowInsensitiveFixedPointComputation 7 | } 8 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 9 | import com.simplytyped.yoyak.framework.domain.{Galois, MapDom, LatticeOps} 10 | import com.simplytyped.yoyak.framework.semantics.{ 11 | Widening, 12 | AbstractTransferable 13 | } 14 | import com.simplytyped.yoyak.il.cfg.{BasicBlock, CFG} 15 | 16 | object BackwardAnalysis { 17 | 18 | // FIXME: we should also take a reverse order while analyzing statements in a single block 19 | class FlowInsensitiveBackwardAnalysis[D <: Galois](val cfg: CFG)(implicit 20 | val ops: LatticeOps[D], 21 | val absTransfer: AbstractTransferable[D], 22 | val widening: Option[Widening[D]] = None 23 | ) extends FlowInsensitiveFixedPointComputation[D] 24 | with WideningAtLoopHeads[D] { 25 | def getNextBlocks(bb: BasicBlock) = cfg.getPrevs(bb).toList 26 | def compute(input: D#Abst): D#Abst = { 27 | val startNodes = cfg.getExit.toList.flatMap { getNextBlocks } 28 | computeFixedPoint(input, startNodes) 29 | } 30 | } 31 | 32 | class FlowSensitiveBackwardAnalysis[D <: Galois](val cfg: CFG)(implicit 33 | val ops: LatticeOps[D], 34 | val absTransfer: AbstractTransferable[D], 35 | val widening: Option[Widening[D]] = None 36 | ) extends FlowSensitiveFixedPointComputation[D] 37 | with WideningAtLoopHeads[D] { 38 | def getNextBlocks(bb: BasicBlock) = cfg.getPrevs(bb).toList 39 | def memoryFetcher(map: MapDom[BasicBlock, D], b: BasicBlock) = 40 | cfg.getNexts(b).toList.map { map.get } 41 | def compute: MapDom[BasicBlock, D] = { 42 | val startNodes = cfg.getExit.toList.flatMap { getNextBlocks } 43 | computeFixedPoint(startNodes) 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/ForwardAnalysis.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework 2 | 3 | import com.simplytyped.yoyak.framework.algo.{ 4 | WideningAtLoopHeads, 5 | FlowSensitiveFixedPointComputation, 6 | FlowInsensitiveFixedPointComputation 7 | } 8 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 9 | import com.simplytyped.yoyak.framework.domain.{Galois, MapDom, LatticeOps} 10 | import com.simplytyped.yoyak.framework.semantics.{ 11 | Widening, 12 | AbstractTransferable 13 | } 14 | import com.simplytyped.yoyak.il.cfg.{BasicBlock, CFG} 15 | 16 | object ForwardAnalysis { 17 | 18 | class FlowInsensitiveForwardAnalysis[D <: Galois](val cfg: CFG)(implicit 19 | val ops: LatticeOps[D], 20 | val absTransfer: AbstractTransferable[D], 21 | val widening: Option[Widening[D]] = None 22 | ) extends FlowInsensitiveFixedPointComputation[D] 23 | with WideningAtLoopHeads[D] { 24 | def getNextBlocks(bb: BasicBlock) = cfg.getNexts(bb).toList 25 | def compute(input: D#Abst): D#Abst = { 26 | val startNodes = cfg.getEntry.toList.flatMap { getNextBlocks } 27 | computeFixedPoint(input, startNodes) 28 | } 29 | } 30 | 31 | class FlowSensitiveForwardAnalysis[D <: Galois](val cfg: CFG)(implicit 32 | val ops: LatticeOps[D], 33 | val absTransfer: AbstractTransferable[D], 34 | val widening: Option[Widening[D]] = None 35 | ) extends FlowSensitiveFixedPointComputation[D] 36 | with WideningAtLoopHeads[D] { 37 | def getNextBlocks(bb: BasicBlock) = cfg.getNexts(bb).toList 38 | def memoryFetcher(map: MapDom[BasicBlock, D], b: BasicBlock) = 39 | cfg.getPrevs(b).toList.map { map.get } 40 | def compute: MapDom[BasicBlock, D] = { 41 | val startNodes = cfg.getEntry.toList.flatMap { getNextBlocks } 42 | computeFixedPoint(startNodes) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/CfgNavigator.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.{Galois, MapDom} 4 | import com.simplytyped.yoyak.il.cfg.BasicBlock 5 | 6 | trait CfgNavigator[D <: Galois] { 7 | def getNextBlocks(bb: BasicBlock): Seq[BasicBlock] 8 | def memoryFetcher(map: MapDom[BasicBlock, D], b: BasicBlock): Seq[D#Abst] 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/DoWidening.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | import com.simplytyped.yoyak.framework.semantics.Widening 5 | import com.simplytyped.yoyak.il.cfg.BasicBlock 6 | 7 | trait DoWidening[D <: Galois] { 8 | protected def doWidening( 9 | widening: Widening[D] 10 | )(x: D#Abst, y: D#Abst, bb: BasicBlock): D#Abst 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/FlowInsensitiveFixedPointComputation.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.{Galois, LatticeOps} 4 | import com.simplytyped.yoyak.framework.semantics.Widening 5 | import com.simplytyped.yoyak.il.cfg.BasicBlock 6 | 7 | trait FlowInsensitiveFixedPointComputation[D <: Galois] 8 | extends FlowInsensitiveIteration[D] 9 | with DoWidening[D] { 10 | implicit val ops: LatticeOps[D] 11 | def getNextBlocks(bb: BasicBlock): Seq[BasicBlock] 12 | 13 | val worklist = Worklist.empty[BasicBlock] 14 | 15 | def computeFixedPoint(input: D#Abst, startNodes: List[BasicBlock])(implicit 16 | widening: Option[Widening[D]] = None 17 | ): D#Abst = { 18 | assert(startNodes.size > 0) 19 | worklist.add(startNodes: _*) 20 | var next = input 21 | while (worklist.size() > 0) { 22 | val bb = worklist.pop().get 23 | val prev = next 24 | next = work(prev, bb) 25 | val isStable = ops.partialCompare(next, prev) 26 | if (isStable.isNaN) 27 | println( 28 | "error: abs. transfer func. is not distributive" 29 | ) // XXX: abstract transfer function is not distributive. should report this error. 30 | if (isStable > 0) { 31 | next = if (widening.nonEmpty) { 32 | doWidening(widening.get)(prev, next, bb) 33 | } else next 34 | val nextWork = getNextBlocks(bb) 35 | worklist.add(nextWork: _*) 36 | } 37 | } 38 | next 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/FlowInsensitiveIteration.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable 5 | import com.simplytyped.yoyak.il.cfg.BasicBlock 6 | 7 | trait FlowInsensitiveIteration[D <: Galois] { 8 | implicit val absTransfer: AbstractTransferable[D] 9 | 10 | protected def work(input: D#Abst, block: BasicBlock): D#Abst = { 11 | val stmts = block.data.getStmts 12 | val output = stmts.foldLeft(input) { absTransfer.transfer } 13 | output 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/FlowSensitiveFixedPointComputation.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.{Galois, MapDom, LatticeOps} 4 | import com.simplytyped.yoyak.framework.semantics.Widening 5 | import com.simplytyped.yoyak.il.cfg.BasicBlock 6 | 7 | trait FlowSensitiveFixedPointComputation[D <: Galois] 8 | extends FlowSensitiveIteration[D] 9 | with CfgNavigator[D] 10 | with DoWidening[D] { 11 | implicit val ops: LatticeOps[D] 12 | 13 | val worklist = Worklist.empty[BasicBlock] 14 | 15 | private def getInput( 16 | map: MapDom[BasicBlock, D], 17 | inputs: Seq[D#Abst] 18 | ): D#Abst = { 19 | val input = inputs.foldLeft(ops.bottom) { (d, i) => 20 | ops.\/(d, i) 21 | } 22 | input 23 | } 24 | 25 | def computeFixedPoint( 26 | startNodes: List[BasicBlock] 27 | )(implicit widening: Option[Widening[D]] = None): MapDom[BasicBlock, D] = { 28 | worklist.add(startNodes: _*) 29 | var map = MapDom.empty[BasicBlock, D] 30 | while (worklist.size() > 0) { 31 | val bb = worklist.pop().get 32 | val prevInputs = memoryFetcher(map, bb) 33 | val prev = getInput(map, prevInputs) 34 | val (mapOut, next) = work(map, prev, bb) 35 | val orig = map.get(bb) 36 | val isStable = ops.partialCompare(next, orig) 37 | if (isStable.isNaN) { 38 | // XXX: abstract transfer function is not distributive. should report this error. 39 | println("error: abs. transfer func. is not distributive") 40 | } 41 | if (isStable > 0) { 42 | val widened = if (widening.nonEmpty) { 43 | doWidening(widening.get)(orig, next, bb) 44 | } else next 45 | map = mapOut.update(bb -> widened) 46 | val nextWork = getNextBlocks(bb) 47 | worklist.add(nextWork: _*) 48 | } 49 | } 50 | map 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/FlowSensitiveIteration.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.{Galois, MapDom} 4 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable 5 | import com.simplytyped.yoyak.il.cfg.BasicBlock 6 | 7 | trait FlowSensitiveIteration[D <: Galois] { 8 | implicit val absTransfer: AbstractTransferable[D] 9 | 10 | protected def work( 11 | map: MapDom[BasicBlock, D], 12 | input: D#Abst, 13 | block: BasicBlock 14 | ): (MapDom[BasicBlock, D], D#Abst) = { 15 | val stmts = block.data.getStmts 16 | val output = stmts.foldLeft(input) { absTransfer.transfer } 17 | (map, output) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/InterproceduralIteration.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 4 | import com.simplytyped.yoyak.framework.domain.MapDom 5 | import com.simplytyped.yoyak.framework.domain.mem.{ 6 | MemDomLike, 7 | Resolvable, 8 | Localizable 9 | } 10 | import com.simplytyped.yoyak.il.CommonIL.Program 11 | import com.simplytyped.yoyak.il.CommonIL.Statement.{Return, Invoke} 12 | import com.simplytyped.yoyak.il.CommonIL.Value.Param 13 | import com.simplytyped.yoyak.il.cfg.BasicEdge.{IntraEdge, EdgeType, InterEdge} 14 | import com.simplytyped.yoyak.il.cfg.{CFG, BasicEdge, BasicBlock} 15 | 16 | trait InterproceduralIteration[A, D, M <: MemDomLike[A, D, M]] 17 | extends FlowSensitiveIteration[GaloisIdentity[M]] 18 | with CfgNavigator[GaloisIdentity[M]] { 19 | implicit val localize: Localizable[M] 20 | implicit val resolve: Resolvable[M] 21 | 22 | val pgm: Program 23 | val worklist: Worklist[BasicBlock] 24 | 25 | var interproceduralEdges: CFG = CFG.empty 26 | 27 | override def getNextBlocks(bb: BasicBlock): Seq[BasicBlock] = { 28 | val methodSigOpt = BasicBlock.getMethodSig(bb) 29 | methodSigOpt 30 | .flatMap { pgm.methods.get } 31 | .flatMap { _.cfg } 32 | .map { _.getNexts(bb).toList } 33 | .getOrElse(List.empty[BasicBlock]) 34 | } 35 | override def memoryFetcher( 36 | map: MapDom[BasicBlock, GaloisIdentity[M]], 37 | b: BasicBlock 38 | ): Seq[M] = { 39 | val methodSigOpt = BasicBlock.getMethodSig(b) 40 | methodSigOpt 41 | .flatMap { pgm.methods.get } 42 | .flatMap { _.cfg } 43 | .flatMap { _.prevs.get(b) } 44 | .map { _.toList.map { e => renameReturnVar(map.get(e.from), e.label) } } 45 | .getOrElse(List.empty[M]) 46 | } 47 | 48 | private def renameReturnVar(input: M, edgeTy: EdgeType): M = { 49 | edgeTy match { 50 | case IntraEdge => input 51 | case InterEdge(retVar) => 52 | // ideally, we should remove all objects where they're unable to be accessed without the return variable 53 | if (retVar.isEmpty) input.remove(localize.returningPlaceholder) 54 | else { 55 | val data = input.get(localize.returningPlaceholder) 56 | input.update(retVar.get -> data) 57 | } 58 | } 59 | } 60 | 61 | override def work( 62 | map: MapDom[BasicBlock, GaloisIdentity[M]], 63 | input: M, 64 | block: BasicBlock 65 | ): (MapDom[BasicBlock, GaloisIdentity[M]], M) = { 66 | val stmts = block.data.getStmts 67 | val (newMap, output) = stmts.foldLeft(map, input) { 68 | case ((m, in), ivk @ Invoke(_, _)) => 69 | (invoking(block, ivk, in, m), absTransfer.transfer(in, ivk)) 70 | case ((m, in), ret @ Return(_)) => 71 | (returning(block, ret, in, m), absTransfer.transfer(in, ret)) 72 | case ((m, in), s) => (m, absTransfer.transfer(in, s)) 73 | } 74 | (newMap, output) 75 | } 76 | 77 | /* 78 | invoking procedure: 79 | 1. map arguments to parameters 80 | 2. remove locals 81 | 3. resolve callees and draw inter-procedural edges 82 | 4. put memory at callees' entries (join?) 83 | 5. push successors of callees' entries into a worklist 84 | 6. push intra-procedural successors of this invoke block into a worklist 85 | (maybe no need to be handled here) 86 | 87 | returning procedure: 88 | 1. remove locals 89 | 2. get a returning value and save it into a returning memory at the special location 90 | something like ($__ret) 91 | 3. put the returning memory at the exit node (join?) 92 | 4. push successors of this exit node into a worklist 93 | 94 | fetching input memory: 95 | 1. get predecessors and their edge types 96 | 2. if the edge type is inter-procedural, 97 | fetch the memory and 98 | rename the special location to the actual return variable as shown in the edge 99 | 3. join all input memories 100 | */ 101 | 102 | def mapArguments(invokeStmt: Invoke, input: M): M = { 103 | val argsWithIndex = invokeStmt.callee.args.map { input.get }.zipWithIndex 104 | val newInput = argsWithIndex.foldLeft(input) { case (m, (v, i)) => 105 | m.update(Param(i) -> v) 106 | } 107 | newInput 108 | } 109 | 110 | def invoking( 111 | block: BasicBlock, 112 | invokeStmt: Invoke, 113 | input: M, 114 | map: MapDom[BasicBlock, GaloisIdentity[M]] 115 | ): MapDom[BasicBlock, GaloisIdentity[M]] = { 116 | val targetMethodSigs = resolve.resolve(input, invokeStmt) 117 | val targetMethods = targetMethodSigs.flatMap { pgm.methods.get } 118 | targetMethods.foldLeft(map) { case (m, targetMethod) => 119 | val argMappedMemory = mapArguments(invokeStmt, input) 120 | val localizedMemory = localize.deleteLocals(argMappedMemory) 121 | var newM = m 122 | for ( 123 | targetCfg <- targetMethod.cfg; 124 | targetEntry <- targetCfg.getEntry; targetExit <- targetCfg.getExit 125 | ) { 126 | 127 | val nextBlocks = getNextBlocks(block) 128 | assert( 129 | nextBlocks.size == 1, 130 | "invoke block can only have one next block" 131 | ) 132 | val nextBlock = nextBlocks.head 133 | val goEdge = BasicEdge(block, targetEntry, InterEdge(None)) 134 | val returnEdge = 135 | BasicEdge(targetExit, nextBlock, InterEdge(invokeStmt.ret)) 136 | interproceduralEdges = 137 | interproceduralEdges.addEdge(goEdge).addEdge(returnEdge) 138 | 139 | newM = m.weakUpdate(targetEntry, localizedMemory) 140 | 141 | val succsOfTargetEntry = targetCfg.getNexts(targetEntry).toList 142 | worklist.add(succsOfTargetEntry: _*) 143 | } 144 | newM 145 | } 146 | } 147 | 148 | def returning( 149 | block: BasicBlock, 150 | returnStmt: Return, 151 | input: M, 152 | map: MapDom[BasicBlock, GaloisIdentity[M]] 153 | ): MapDom[BasicBlock, GaloisIdentity[M]] = { 154 | val returnValOpt = returnStmt.v.map { input.get } 155 | val localizedMemory = localize.deleteLocals(input) 156 | val returningMemory = returnValOpt.foldLeft(localizedMemory) { 157 | case (m, ret) => m.update(localize.returningPlaceholder, ret) 158 | } 159 | val exitNode = getNextBlocks(block) 160 | exitNode.foldLeft(map) { case (m, exitNode) => 161 | val succsOfExit = interproceduralEdges.getNexts(exitNode).toList 162 | worklist.add(succsOfExit: _*) 163 | m.weakUpdate(exitNode -> returningMemory) 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/WideningAtLoopHeads.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | import com.simplytyped.yoyak.framework.semantics.Widening 5 | import com.simplytyped.yoyak.il.cfg.{BasicBlock, CFG} 6 | 7 | trait WideningAtLoopHeads[D <: Galois] extends DoWidening[D] { 8 | val cfg: CFG 9 | 10 | val loopHeads = CFG.traverse.findLoopheads(cfg).toSet 11 | protected def doWidening( 12 | widening: Widening[D] 13 | )(x: D#Abst, y: D#Abst, bb: BasicBlock): D#Abst = { 14 | if (loopHeads(bb)) { 15 | widening.<>(x, y) 16 | } else y 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/algo/Worklist.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.algo 2 | 3 | class Worklist[E] { 4 | var list = Vector.empty[E] 5 | def add(elems: E*): Worklist[E] = { 6 | list = list ++ elems 7 | this 8 | } 9 | def pop(): Option[E] = { 10 | val elem = list.headOption 11 | if (elem.nonEmpty) list = list.drop(1) 12 | elem 13 | } 14 | def size() = list.size 15 | } 16 | 17 | object Worklist { 18 | def empty[E] = new Worklist[E] 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/ArithmeticOps.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Value.Constant 4 | 5 | trait ArithmeticOps[D <: Galois] extends LatticeWithTopOps[D] { 6 | def +(lhs: D#Abst, rhs: D#Abst): D#Abst 7 | def -(lhs: D#Abst, rhs: D#Abst): D#Abst 8 | def *(lhs: D#Abst, rhs: D#Abst): D#Abst 9 | def /(lhs: D#Abst, rhs: D#Abst): D#Abst 10 | 11 | def lift(const: Constant): D#Abst 12 | def unlift(abs: D#Abst): Option[Set[D#Conc]] 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/Galois.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain 2 | 3 | trait Galois { 4 | type Conc 5 | type Abst 6 | } 7 | 8 | object Galois { 9 | class GaloisIdentity[A] extends Galois { 10 | type Conc = A 11 | type Abst = A 12 | } 13 | class SetAbstraction[A] extends Galois { 14 | type Conc = A 15 | type Abst = Set[A] 16 | } 17 | object SetAbstraction { 18 | implicit def boxedOps[A]: LatticeWithTopOps[SetAbstraction[A]] = 19 | new LatticeWithTopOps[SetAbstraction[A]] { 20 | override def isTop(v: Set[A]): Boolean = false 21 | 22 | override def bottom: Set[A] = Set.empty[A] 23 | 24 | override def \/(lhs: Set[A], rhs: Set[A]): Set[A] = lhs ++ rhs 25 | 26 | override def partialCompare(lhs: Set[A], rhs: Set[A]): Double = 27 | if (lhs == rhs) 0.0 28 | else if (lhs subsetOf rhs) -1.0 29 | else if (rhs subsetOf lhs) 1.0 30 | else Double.NaN 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/LatticeOps.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain 2 | 3 | import cats.PartialOrder 4 | 5 | trait LatticeOps[D <: Galois] extends PartialOrder[D#Abst] { 6 | def \/(lhs: D#Abst, rhs: D#Abst): D#Abst 7 | def bottom: D#Abst 8 | 9 | def isBottom(v: D#Abst): Boolean = { 10 | val isBot = partialCompare(v, bottom) 11 | assert(!isBot.isNaN, "every element should have an order with bottom") 12 | isBot == 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/LatticeWithTopOps.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain 2 | 3 | trait LatticeWithTopOps[D <: Galois] extends LatticeOps[D] { 4 | def isTop(v: D#Abst): Boolean 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/MapDom.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 4 | import com.simplytyped.yoyak.framework.semantics.Widening 5 | 6 | class MapDom[K, V <: Galois: LatticeOps] { 7 | val valueOps = implicitly[LatticeOps[V]] 8 | 9 | private var rawMap = Map.empty[K, V#Abst] 10 | 11 | override def toString = rawMap.mkString("\n") 12 | 13 | private def add(kv: (K, V#Abst)): MapDom[K, V] = { 14 | if (!valueOps.isBottom(kv._2)) { 15 | val newDom = new MapDom[K, V] 16 | newDom.rawMap = rawMap + (kv._1 -> kv._2) 17 | newDom 18 | } else this 19 | } 20 | def update(kv: (K, V#Abst)): MapDom[K, V] = add(kv) 21 | def weakUpdate(kv: (K, V#Abst)): MapDom[K, V] = 22 | add(kv._1 -> valueOps.\/(get(kv._1), kv._2)) 23 | def get(k: K): V#Abst = rawMap.getOrElse(k, valueOps.bottom) 24 | def remove(k: K): MapDom[K, V] = { 25 | if (rawMap.get(k).nonEmpty) { 26 | val newDom = new MapDom[K, V] 27 | newDom.rawMap = rawMap - k 28 | newDom 29 | } else this 30 | } 31 | 32 | def foldLeft[T](init: T)(f: (T, (K, V#Abst)) => T): T = 33 | rawMap.foldLeft(init)(f) 34 | def size: Int = rawMap.size 35 | def iterator = rawMap.iterator 36 | def keySet = rawMap.keySet 37 | def head = rawMap.head 38 | } 39 | 40 | object MapDom { 41 | def empty[K, V <: Galois: LatticeOps] = new MapDom[K, V] 42 | 43 | def widening[K, V <: Galois: LatticeOps: Widening] = 44 | new Widening[GaloisIdentity[MapDom[K, V]]] { 45 | val widening = implicitly[Widening[V]] 46 | override def <>(x: MapDom[K, V], y: MapDom[K, V]): MapDom[K, V] = { 47 | val newDom = new MapDom[K, V] 48 | newDom.rawMap = y.rawMap.map { case (k, v) => 49 | val oldV = x.get(k) 50 | val newV = widening.<>(oldV, v) 51 | (k, newV) 52 | } 53 | newDom 54 | } 55 | } 56 | 57 | def ops[K, V <: Galois: LatticeOps] = 58 | new LatticeOps[GaloisIdentity[MapDom[K, V]]] { 59 | val valueOps = implicitly[LatticeOps[V]] 60 | 61 | override def partialCompare( 62 | lhs: MapDom[K, V], 63 | rhs: MapDom[K, V] 64 | ): Double = { 65 | // short cuts 66 | if (lhs.size == 0 && rhs.size == 0) return 0.0 67 | if (lhs.size == 0 && rhs.size > 0) return -1.0 68 | if (rhs.size == 0 && lhs.size > 0) return 1.0 69 | 70 | if (lhs.size < rhs.size) { 71 | val lhsIter = lhs.iterator 72 | var flag = true 73 | while (flag && lhsIter.hasNext) { 74 | val (k, v) = lhsIter.next() 75 | val rValue = rhs.get(k) 76 | val order = valueOps.partialCompare(v, rValue) 77 | if (order.isNaN || order > 0) flag = false 78 | } 79 | if (flag) -1.0 else Double.NaN 80 | } else if (lhs.size > rhs.size) { 81 | val rhsIter = rhs.iterator 82 | var flag = true 83 | while (flag && rhsIter.hasNext) { 84 | val (k, v) = rhsIter.next() 85 | val lValue = lhs.get(k) 86 | val order = valueOps.partialCompare(v, lValue) 87 | if (order.isNaN || order > 0) flag = false 88 | } 89 | if (flag) 1.0 else Double.NaN 90 | } else { 91 | if (lhs.keySet != rhs.keySet) Double.NaN 92 | else { 93 | // lhs.size != 0 && rhs.size != 0 94 | // find initial order 95 | val lhsIter = lhs.iterator 96 | var order = 0.0 97 | while (!order.isNaN && order == 0 && lhsIter.hasNext) { 98 | val (k, lv) = lhsIter.next() 99 | val rv = rhs.get(k) 100 | order = valueOps.partialCompare(lv, rv) 101 | } 102 | if (!order.isNaN && order == 0 && !lhsIter.hasNext) 0.0 // equal 103 | else if (order.isNaN) Double.NaN 104 | else { 105 | // order != 0 106 | if (order < 0) { 107 | // assume: lhs < rhs 108 | // this logic is copied from above 109 | var flag = true 110 | while (flag && lhsIter.hasNext) { 111 | val (k, lValue) = lhsIter.next() 112 | val rValue = rhs.get(k) 113 | val order = valueOps.partialCompare(lValue, rValue) 114 | if (order.isNaN || order > 0) flag = false 115 | } 116 | if (flag) -1.0 else Double.NaN 117 | } else { 118 | // assume: lhs > rhs 119 | // this logic is copied from above 120 | var flag = true 121 | while (flag && lhsIter.hasNext) { 122 | val (k, lValue) = lhsIter.next() 123 | val rValue = rhs.get(k) 124 | val order = valueOps.partialCompare(rValue, lValue) 125 | if (order.isNaN || order > 0) flag = false 126 | } 127 | if (flag) 1.0 else Double.NaN 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | override def \/(lhs: MapDom[K, V], rhs: MapDom[K, V]): MapDom[K, V] = { 135 | rhs.foldLeft(lhs) { case (map, (k, v)) => map.weakUpdate(k, v) } 136 | } 137 | 138 | override val bottom: MapDom[K, V] = MapDom.empty[K, V] 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/arith/Interval.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.arith 2 | 3 | import com.simplytyped.yoyak.framework.domain.arith.Interval._ 4 | 5 | sealed abstract class Interval 6 | case class Interv private (lb: IntWithInfMinus, ub: IntWithInf) 7 | extends Interval { 8 | assert( 9 | ub == IInf || lb == IInfMinus || 10 | (lb.isInstanceOf[IInt] && ub.isInstanceOf[IInt] && lb 11 | .asInstanceOf[IInt] 12 | .v <= ub.asInstanceOf[IInt].v) 13 | ) 14 | } 15 | object Interv { 16 | def in(lb: IntWithInfMinus, ub: IntWithInf): Interval = { 17 | if (lb == IInfMinus && ub == IInf) IntervTop 18 | else Interv(lb, ub) 19 | } 20 | def of(v: Int): Interval = { Interv(IInt(v), IInt(v)) } 21 | } 22 | case object IntervBottom extends Interval 23 | case object IntervTop extends Interval 24 | 25 | object Interval { 26 | abstract class IntWithInfs extends Ordered[IntWithInfs] { 27 | override def compare(that: IntWithInfs): Int = { 28 | (this, that) match { 29 | case (IInf, IInf) => 0 30 | case (IInf, _) => 1 31 | case (IInfMinus, IInfMinus) => 0 32 | case (IInfMinus, _) => -1 33 | case (IInt(_), IInf) => -1 34 | case (IInt(_), IInfMinus) => 1 35 | case (IInt(v1), IInt(v2)) => v1 - v2 36 | } 37 | } 38 | } 39 | sealed trait IntWithInf extends IntWithInfs 40 | sealed trait IntWithInfMinus extends IntWithInfs 41 | 42 | case class IInt(v: Int) extends IntWithInf with IntWithInfMinus 43 | case object IInf extends IntWithInf 44 | case object IInfMinus extends IntWithInfMinus 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/arith/IntervalInt.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.arith 2 | 3 | import com.simplytyped.yoyak.framework.domain.arith.Interval._ 4 | import com.simplytyped.yoyak.framework.domain.{ArithmeticOps, Galois} 5 | import com.simplytyped.yoyak.framework.semantics.Widening 6 | import com.simplytyped.yoyak.il.CommonIL.Value.{IntegerConstant, Constant} 7 | 8 | import scala.util.Sorting 9 | 10 | class IntervalInt extends Galois { 11 | type Conc = Int 12 | type Abst = Interval 13 | } 14 | 15 | object IntervalInt { 16 | implicit val arithOps: ArithmeticOps[IntervalInt] = 17 | new ArithmeticOps[IntervalInt] { 18 | override def +(lhs: Interval, rhs: Interval): Interval = { 19 | (lhs, rhs) match { 20 | case (IntervBottom, _) => IntervBottom 21 | case (_, IntervBottom) => IntervBottom 22 | case (IntervTop, _) => IntervTop 23 | case (_, IntervTop) => IntervTop 24 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 25 | val newlb = (lb1, lb2) match { 26 | /* lb1 + lb2 */ 27 | case (IInfMinus, _) => IInfMinus 28 | case (_, IInfMinus) => IInfMinus 29 | case (IInt(v1), IInt(v2)) => IInt(v1 + v2) 30 | } 31 | val newub = (ub1, ub2) match { 32 | /* ub1 + ub2 */ 33 | case (IInf, _) => IInf 34 | case (_, IInf) => IInf 35 | case (IInt(v1), IInt(v2)) => IInt(v1 + v2) 36 | } 37 | Interv.in(newlb, newub) 38 | } 39 | } 40 | 41 | override def /(lhs: Interval, rhs: Interval): Interval = { 42 | def divBound(a: IntWithInfs, b: IntWithInfs): Option[IntWithInfs] = { 43 | (a, b) match { 44 | case (IInf, IInf) => Some(IInf) 45 | case (IInf, IInfMinus) => Some(IInfMinus) 46 | case (IInf, IInt(v)) => 47 | if (v > 0) Some(IInf) else if (v == 0) None else Some(IInfMinus) 48 | case (IInfMinus, IInf) => Some(IInfMinus) 49 | case (IInfMinus, IInfMinus) => Some(IInf) 50 | case (IInfMinus, IInt(v)) => 51 | if (v > 0) Some(IInfMinus) else if (v == 0) None else Some(IInf) 52 | case (IInt(_), IInf) => Some(IInt(0)) 53 | case (IInt(_), IInfMinus) => Some(IInt(0)) 54 | case (IInt(v1), IInt(v2)) => 55 | if (v2 != 0) Some(IInt(v1 / v2)) else None 56 | } 57 | } 58 | (lhs, rhs) match { 59 | case (IntervBottom, _) => IntervBottom 60 | case (_, IntervBottom) => IntervBottom 61 | case (IntervTop, _) => IntervTop 62 | case (_, IntervTop) => IntervTop 63 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 64 | val values = Array( 65 | divBound(lb1, lb2), 66 | divBound(lb1, ub2), 67 | divBound(ub1, lb2), 68 | divBound(ub1, ub2) 69 | ).flatten 70 | if (values.isEmpty) IntervBottom 71 | else { 72 | Sorting.quickSort(values) 73 | val newlb = 74 | values.head 75 | .asInstanceOf[IntWithInfMinus] // this couldn't be IInf 76 | val newub = 77 | values.last 78 | .asInstanceOf[IntWithInf] // this couldn't be IInfMinus 79 | Interv.in(newlb, newub) 80 | } 81 | } 82 | } 83 | 84 | override def lift(const: Constant): Interval = { 85 | const match { 86 | case IntegerConstant(v) => Interv.of(v) 87 | case _ => IntervBottom // hmm... 88 | } 89 | } 90 | 91 | override def -(lhs: Interval, rhs: Interval): Interval = { 92 | (lhs, rhs) match { 93 | case (IntervBottom, _) => IntervBottom 94 | case (_, IntervBottom) => IntervBottom 95 | case (IntervTop, _) => IntervTop 96 | case (_, IntervTop) => IntervTop 97 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 98 | val newlb = (lb1, ub2) match { 99 | /* lb1 - ub2 */ 100 | case (IInfMinus, _) => IInfMinus 101 | case (_, IInf) => IInfMinus 102 | case (IInt(v1), IInt(v2)) => IInt(v1 - v2) 103 | } 104 | val newub = (ub1, lb2) match { 105 | /* ub1 - lb2 */ 106 | case (IInf, _) => IInf 107 | case (_, IInfMinus) => IInf 108 | case (IInt(v1), IInt(v2)) => IInt(v1 - v2) 109 | } 110 | Interv.in(newlb, newub) 111 | } 112 | } 113 | 114 | override def *(lhs: Interval, rhs: Interval): Interval = { 115 | def multBound(a: IntWithInfs, b: IntWithInfs): IntWithInfs = { 116 | (a, b) match { 117 | case (IInf, IInf) => IInf 118 | case (IInf, IInfMinus) => IInfMinus 119 | case (IInf, IInt(v)) => 120 | if (v > 0) IInf else if (v == 0) IInt(0) else IInfMinus 121 | case (IInfMinus, IInf) => multBound(b, a) 122 | case (IInfMinus, IInfMinus) => IInf 123 | case (IInfMinus, IInt(v)) => 124 | if (v > 0) IInfMinus else if (v == 0) IInt(0) else IInf 125 | case (IInt(_), IInf) => multBound(b, a) 126 | case (IInt(_), IInfMinus) => multBound(b, a) 127 | case (IInt(v1), IInt(v2)) => IInt(v1 * v2) 128 | } 129 | } 130 | (lhs, rhs) match { 131 | case (IntervBottom, _) => IntervBottom 132 | case (_, IntervBottom) => IntervBottom 133 | case (IntervTop, _) => IntervTop 134 | case (_, IntervTop) => IntervTop 135 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 136 | val values = Array( 137 | multBound(lb1, lb2), 138 | multBound(lb1, ub2), 139 | multBound(ub1, lb2), 140 | multBound(ub1, ub2) 141 | ) 142 | Sorting.quickSort(values) 143 | val newlb = 144 | values.head.asInstanceOf[IntWithInfMinus] // this couldn't be IInf 145 | val newub = 146 | values.last.asInstanceOf[IntWithInf] // this couldn't be IInfMinus 147 | Interv.in(newlb, newub) 148 | } 149 | } 150 | 151 | override def unlift(abs: Interval): Option[Set[Int]] = { 152 | abs match { 153 | case IntervBottom => Some(Set.empty) 154 | case Interv(IInt(v1), IInt(v2)) => Some((v1 to v2).toSet) 155 | case _ => 156 | None // impossible to concretize infinite sets. better ideas? 157 | } 158 | } 159 | 160 | override def isTop(v: Interval): Boolean = { v == IntervTop } 161 | 162 | override def bottom: Interval = IntervBottom 163 | 164 | override def \/(lhs: Interval, rhs: Interval): Interval = 165 | (lhs, rhs) match { 166 | case (IntervTop, _) => IntervTop 167 | case (_, IntervTop) => IntervTop 168 | case (IntervBottom, x) => x 169 | case (x, IntervBottom) => x 170 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 171 | val newLb = (lb1, lb2) match { 172 | case (IInfMinus, _) => IInfMinus 173 | case (_, IInfMinus) => IInfMinus 174 | case (IInt(v1), IInt(v2)) => IInt(Math.min(v1, v2)) 175 | } 176 | val newUb = (ub1, ub2) match { 177 | case (IInf, _) => IInf 178 | case (_, IInf) => IInf 179 | case (IInt(v1), IInt(v2)) => IInt(Math.max(v1, v2)) 180 | } 181 | Interv.in(newLb, newUb) 182 | } 183 | 184 | override def partialCompare(lhs: Interval, rhs: Interval): Double = 185 | (lhs, rhs) match { 186 | case (IntervTop, IntervTop) => 0.0 187 | case (_, IntervTop) => -1.0 188 | case (IntervTop, _) => 1.0 189 | case (IntervBottom, IntervBottom) => 0.0 190 | case (IntervBottom, _) => -1.0 191 | case (_, IntervBottom) => 1.0 192 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 193 | val lbOrder = (lb1, lb2) match { 194 | case (IInfMinus, IInfMinus) => 0 195 | case (IInfMinus, _) => -1 196 | case (_, IInfMinus) => 1 197 | case (IInt(v1), IInt(v2)) => v1 compare v2 198 | } 199 | val ubOrder = (ub1, ub2) match { 200 | case (IInf, IInf) => 0 201 | case (IInf, _) => 1 202 | case (_, IInf) => -1 203 | case (IInt(v1), IInt(v2)) => v1 compare v2 204 | } 205 | (lbOrder, ubOrder) match { 206 | case (x, y) if x == 0 && y == 0 => 0.0 207 | case (x, y) if x >= 0 && y <= 0 => -1.0 208 | case (x, y) if x <= 0 && y >= 0 => 1.0 209 | case _ => Double.NaN 210 | } 211 | } 212 | } 213 | implicit val widening = new Widening[IntervalInt] { 214 | override def <>(x: Interval, y: Interval): Interval = { 215 | (x, y) match { 216 | case (IntervTop, _) => IntervTop 217 | case (_, IntervTop) => IntervTop 218 | case (IntervBottom, x) => x 219 | case (_, IntervBottom) => IntervBottom 220 | case (Interv(lb1, ub1), Interv(lb2, ub2)) => 221 | val newLb = (lb1, lb2) match { 222 | case (IInt(v1), IInt(v2)) if v1 <= v2 => IInt(v1) 223 | case _ => IInfMinus 224 | } 225 | val newUb = (ub1, ub2) match { 226 | case (IInt(v1), IInt(v2)) if v2 <= v1 => IInt(v1) 227 | case _ => IInf 228 | } 229 | Interv.in(newLb, newUb) 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/ArrayJoinModel.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.{ 4 | Galois, 5 | LatticeWithTopOps, 6 | ArithmeticOps 7 | } 8 | import com.simplytyped.yoyak.framework.domain.mem.MemElems.AbsValue 9 | import com.simplytyped.yoyak.il.CommonIL.Value.{InstanceFieldRef, ArrayRef} 10 | import ArrayJoinModel._ 11 | 12 | trait ArrayJoinModel[A <: Galois, D <: Galois, This <: ArrayJoinModel[ 13 | A, 14 | D, 15 | This 16 | ]] extends MemDomLike[A, D, This] { 17 | implicit val arithOps: ArithmeticOps[A] 18 | implicit val boxedOps: LatticeWithTopOps[D] 19 | 20 | protected def updateArray(kv: (ArrayRef, AbsValue[A, D])): This = { 21 | val (arrayref, v) = kv 22 | val dummyRef = InstanceFieldRef(arrayref.base, arrayFieldName) 23 | val orig = get(dummyRef) 24 | update(dummyRef -> AbsValue.ops[A, D].\/(orig, v)) 25 | } 26 | protected def getArray(k: ArrayRef): AbsValue[A, D] = { 27 | get(InstanceFieldRef(k.base, arrayFieldName)) 28 | } 29 | } 30 | 31 | object ArrayJoinModel { 32 | val arrayFieldName = "__joined_array_elems" 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/Localizable.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Value.Local 4 | 5 | trait Localizable[D <: MemDomLike[_, _, D]] { 6 | val returningPlaceholder: Local 7 | def deleteLocals(input: D): D 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/MemDom.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 4 | import com.simplytyped.yoyak.framework.domain._ 5 | import com.simplytyped.yoyak.framework.domain.mem.MemElems._ 6 | import com.simplytyped.yoyak.framework.semantics.Widening 7 | 8 | class MemDom[A <: Galois: ArithmeticOps, D <: Galois: LatticeWithTopOps] 9 | extends StdObjectModel[A, D, MemDom[A, D]] { 10 | 11 | lazy val arithOps: ArithmeticOps[A] = implicitly[ArithmeticOps[A]] 12 | lazy val boxedOps: LatticeWithTopOps[D] = implicitly[LatticeWithTopOps[D]] 13 | 14 | override protected def builder( 15 | rawMap: MapDom[AbsAddr, GaloisIdentity[AbsValue[A, D]]] 16 | ): MemDom[A, D] = { 17 | val newMemDom = new MemDom[A, D] 18 | newMemDom.rawMap = rawMap 19 | newMemDom 20 | } 21 | } 22 | 23 | object MemDom { 24 | def empty[A <: Galois: ArithmeticOps, D <: Galois: LatticeWithTopOps] = 25 | new MemDom[A, D] 26 | 27 | def widening[ 28 | A <: Galois: Widening: ArithmeticOps, 29 | D <: Galois: Widening: LatticeWithTopOps 30 | ] = 31 | new Widening[GaloisIdentity[MemDom[A, D]]] { 32 | implicit val wideningAbsValue = StdObjectModel.wideningWithObject[A, D] 33 | implicit val absValueOps = StdObjectModel.absValueOpsWithObject[A, D] 34 | 35 | override def <>(x: MemDom[A, D], y: MemDom[A, D]): MemDom[A, D] = { 36 | val newRawMap = MapDom 37 | .widening[AbsAddr, GaloisIdentity[AbsValue[A, D]]] 38 | .<>(x.rawMap, y.rawMap) 39 | val newMemDom = new MemDom[A, D] 40 | newMemDom.rawMap = newRawMap 41 | newMemDom 42 | } 43 | } 44 | 45 | def ops[A <: Galois: ArithmeticOps, D <: Galois: LatticeWithTopOps] = 46 | new LatticeOps[GaloisIdentity[MemDom[A, D]]] { 47 | implicit val absValueOps = StdObjectModel.absValueOpsWithObject[A, D] 48 | 49 | override def partialCompare( 50 | lhs: MemDom[A, D], 51 | rhs: MemDom[A, D] 52 | ): Double = { 53 | MapDom 54 | .ops[AbsAddr, GaloisIdentity[AbsValue[A, D]]] 55 | .partialCompare(lhs.rawMap, rhs.rawMap) 56 | } 57 | 58 | override def \/(lhs: MemDom[A, D], rhs: MemDom[A, D]): MemDom[A, D] = { 59 | val newRawMap = MapDom 60 | .ops[AbsAddr, GaloisIdentity[AbsValue[A, D]]] 61 | .\/(lhs.rawMap, rhs.rawMap) 62 | val newMemDom = new MemDom[A, D] 63 | newMemDom.rawMap = newRawMap 64 | newMemDom 65 | } 66 | 67 | override val bottom: MemDom[A, D] = MemDom.empty[A, D] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/MemDomLike.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.mem.MemElems.{ 4 | AbsRef, 5 | AbsAddr, 6 | AbsValue 7 | } 8 | import com.simplytyped.yoyak.il.CommonIL.Statement.Stmt 9 | import com.simplytyped.yoyak.il.CommonIL.Value.{Local, Loc} 10 | 11 | trait MemDomLike[A, D, This <: MemDomLike[A, D, This]] { 12 | def update(kv: (Loc, AbsValue[A, D])): This 13 | def remove(loc: Local): This 14 | def alloc(from: Stmt): (AbsRef, This) 15 | def get(k: Loc): AbsValue[A, D] 16 | def isStaticAddr(addr: AbsAddr): Boolean 17 | def isDynamicAddr(addr: AbsAddr): Boolean 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/MemElems.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 4 | import com.simplytyped.yoyak.framework.domain._ 5 | import com.simplytyped.yoyak.framework.semantics.Widening 6 | import com.simplytyped.yoyak.il.cfg.BasicBlock 7 | 8 | object MemElems { 9 | case class AbsAddr(id: String) { 10 | def toAbsRef: AbsRef = AbsRef(Set(id)) 11 | } 12 | 13 | abstract class AbsValue[+A, +D] 14 | 15 | case class AbsRef(id: Set[String]) extends AbsValue 16 | case class AbsArith[A <: Galois](data: A#Abst) extends AbsValue[A, Nothing] 17 | case class AbsBox[D <: Galois](data: D#Abst) extends AbsValue[Nothing, D] 18 | case object AbsBottom extends AbsValue 19 | case object AbsTop extends AbsValue 20 | 21 | object AbsValue { 22 | def widening[A <: Galois: Widening, D <: Galois: Widening] = 23 | new Widening[GaloisIdentity[AbsValue[A, D]]] { 24 | val wideningA = implicitly[Widening[A]] 25 | val wideningD = implicitly[Widening[D]] 26 | override def <>( 27 | x: AbsValue[A, D], 28 | y: AbsValue[A, D] 29 | ): AbsValue[A, D] = { 30 | (x, y) match { 31 | case (AbsArith(a1), AbsArith(a2)) => AbsArith(wideningA.<>(a1, a2)) 32 | case (AbsBox(d1), AbsBox(d2)) => AbsBox(wideningD.<>(d1, d2)) 33 | case (_, _) => y 34 | } 35 | } 36 | } 37 | def ops[A <: Galois: ArithmeticOps, D <: Galois: LatticeWithTopOps] = 38 | new LatticeOps[GaloisIdentity[AbsValue[A, D]]] { 39 | val boxOps = implicitly[LatticeWithTopOps[D]] 40 | val arithOps = implicitly[ArithmeticOps[A]] 41 | override def partialCompare( 42 | lhs: AbsValue[A, D], 43 | rhs: AbsValue[A, D] 44 | ): Double = { 45 | (lhs, rhs) match { 46 | case (AbsBottom, AbsBottom) => 0.0 47 | case (AbsBottom, _) => -1.0 48 | case (_, AbsBottom) => 1.0 49 | case (AbsTop, AbsTop) => 0.0 50 | case (_, AbsTop) => -1.0 51 | case (AbsTop, _) => 1.0 52 | case (AbsRef(id1), AbsRef(id2)) => 53 | if (id1 == id2) 0.0 54 | else if (id1 subsetOf id2) -1.0 55 | else if (id2 subsetOf id1) 1.0 56 | else Double.NaN 57 | case (AbsBox(data1), AbsBox(data2)) => 58 | boxOps.partialCompare(data1, data2) 59 | case (AbsArith(data1), AbsArith(data2)) => 60 | arithOps.partialCompare(data1, data2) 61 | case (_, _) => Double.NaN 62 | } 63 | } 64 | 65 | override def \/( 66 | lhs: AbsValue[A, D], 67 | rhs: AbsValue[A, D] 68 | ): AbsValue[A, D] = { 69 | (lhs, rhs) match { 70 | case (AbsBottom, x) => x 71 | case (x, AbsBottom) => x 72 | case (AbsTop, _) => AbsTop 73 | case (_, AbsTop) => AbsTop 74 | case (AbsRef(id1), AbsRef(id2)) => AbsRef(id1 ++ id2) 75 | case (AbsBox(data1), AbsBox(data2)) => 76 | val joined = boxOps.\/(data1, data2) 77 | if (boxOps.isTop(joined)) AbsTop 78 | else AbsBox(joined) 79 | case (AbsArith(data1), AbsArith(data2)) => 80 | val joined = arithOps.\/(data1, data2) 81 | if (arithOps.isTop(joined)) AbsTop 82 | else AbsArith(joined) 83 | case (_, _) => AbsTop 84 | } 85 | } 86 | 87 | override val bottom: AbsValue[A, D] = AbsBottom 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/Resolvable.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.MethodSig 4 | import com.simplytyped.yoyak.il.CommonIL.Statement.Invoke 5 | 6 | trait Resolvable[D <: MemDomLike[_, _, D]] { 7 | def resolve(mem: D, invokeStmt: Invoke): List[MethodSig] 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/SimpleLocalizer.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.mem.StdObjectModel.AbsObject 4 | import com.simplytyped.yoyak.framework.domain.{ 5 | Galois, 6 | LatticeWithTopOps, 7 | ArithmeticOps 8 | } 9 | import com.simplytyped.yoyak.framework.domain.mem.MemElems.{ 10 | AbsValue, 11 | AbsRef, 12 | AbsAddr 13 | } 14 | import com.simplytyped.yoyak.il.CommonIL.Value.Local 15 | 16 | class SimpleLocalizer[ 17 | A <: Galois: ArithmeticOps, 18 | D <: Galois: LatticeWithTopOps 19 | ] extends Localizable[MemDom[A, D]] { 20 | val returningPlaceholder = Local("$__ret") 21 | def deleteLocals(input: MemDom[A, D]): MemDom[A, D] = { 22 | val safeAddrs = input.rawMap.foldLeft(Set.empty[AbsAddr]) { 23 | case (s, (a, _)) => 24 | a match { 25 | case _ if input.isStaticAddr(a) => s + a 26 | case _ if a.id == returningPlaceholder.id => s + a 27 | case _ => s 28 | } 29 | } 30 | moveObjects(input, safeAddrs) 31 | } 32 | private def moveObjects( 33 | oldMem: MemDom[A, D], 34 | targets: Set[AbsAddr], 35 | newMem: MemDom[A, D] = MemDom.empty 36 | ): MemDom[A, D] = { 37 | if (targets.isEmpty) newMem 38 | else { 39 | val (newMemory, newTargets) = 40 | targets.foldLeft(newMem, Set.empty[AbsAddr]) { case ((m, s), a) => 41 | val value = oldMem.rawMap.get(a) 42 | val ns = getAddrsFromValue(value) 43 | val nm = MemDom.empty[A, D] 44 | nm.rawMap = m.rawMap.update(a, value) 45 | (nm, ns) 46 | } 47 | moveObjects(oldMem, newTargets, newMemory) 48 | } 49 | } 50 | private def getAddrsFromValue(v: AbsValue[A, D]): Set[AbsAddr] = { 51 | v match { 52 | case obj: AbsObject[A, D] => 53 | obj.rawFieldMap.foldLeft(Set.empty[AbsAddr]) { 54 | case (s, (_, fieldValue)) => s ++ getAddrsFromValue(fieldValue) 55 | } 56 | case AbsRef(ids) => ids.map { AbsAddr } 57 | case _ => Set.empty 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/SimpleResolver.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | import com.simplytyped.yoyak.il.CommonIL.MethodSig 5 | import com.simplytyped.yoyak.il.CommonIL.Statement.Invoke 6 | import com.simplytyped.yoyak.il.CommonIL.Type.{StaticInvoke, DynamicInvoke} 7 | 8 | class SimpleResolver[A <: Galois, D <: Galois] 9 | extends Resolvable[MemDom[A, D]] { 10 | def resolve(mem: MemDom[A, D], invokeStmt: Invoke): List[MethodSig] = { 11 | invokeStmt match { 12 | case Invoke(_, DynamicInvoke(callee, args, base)) => List(callee) 13 | case Invoke(_, StaticInvoke(callee, args)) => List(callee) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/domain/mem/StdObjectModel.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 4 | import com.simplytyped.yoyak.framework.domain._ 5 | import com.simplytyped.yoyak.framework.domain.mem.MemElems._ 6 | import com.simplytyped.yoyak.framework.semantics.Widening 7 | import com.simplytyped.yoyak.il.CommonIL.ClassName 8 | import com.simplytyped.yoyak.il.CommonIL.Statement.Stmt 9 | import com.simplytyped.yoyak.il.CommonIL.Value._ 10 | import StdObjectModel._ 11 | 12 | trait StdObjectModel[A <: Galois, D <: Galois, This <: StdObjectModel[ 13 | A, 14 | D, 15 | This 16 | ]] extends MemDomLike[A, D, This] 17 | with ArrayJoinModel[A, D, This] { 18 | implicit val arithOps: ArithmeticOps[A] 19 | implicit val boxedOps: LatticeWithTopOps[D] 20 | 21 | implicit val absValueOps = absValueOpsWithObject[A, D] 22 | 23 | protected[mem] var rawMap = 24 | MapDom.empty[AbsAddr, GaloisIdentity[AbsValue[A, D]]] 25 | 26 | def alloc(from: Stmt): (AbsRef, This) = { 27 | val newObj = new AbsObject[A, D] 28 | val newAddr = getNewAddr(from) 29 | val newRawMap = rawMap.update(newAddr -> newObj) 30 | (newAddr.toAbsRef, builder(newRawMap)) 31 | } 32 | def remove(loc: Local): This = { 33 | val newRawMap = rawMap.remove(AbsAddr(loc.id)) 34 | builder(newRawMap) 35 | } 36 | private def updateRawMap( 37 | map: MapDom[AbsAddr, GaloisIdentity[AbsValue[A, D]]], 38 | kv: (Loc, AbsValue[A, D]) 39 | ): MapDom[AbsAddr, GaloisIdentity[AbsValue[A, D]]] = { 40 | val (loc, v) = kv 41 | loc match { 42 | case Local(id) => map.update(AbsAddr(id) -> v) 43 | case loc: ArrayRef => updateArray(loc, v).rawMap 44 | case InstanceFieldRef(base, field) => 45 | val objRef = get(base) 46 | objRef match { 47 | case AbsRef(ids) => 48 | ids.foldLeft(map) { (m, i) => 49 | val obj = map.get(AbsAddr(i)) 50 | val newObj = obj match { 51 | case obj: AbsObject[A, D] => 52 | if (ids.size == 1) obj.updateField(field -> v) 53 | else obj.weakUpdateField(field -> v) 54 | case _ => obj // error case: should be reported 55 | } 56 | m.update(AbsAddr(i) -> newObj) 57 | } 58 | case _ => map // error case: should be reported 59 | } 60 | case StaticFieldRef(clazz, field) => 61 | val staticAddr = getStaticAddr(clazz) 62 | val staticObj = map.get(staticAddr) 63 | val newStaticObj = staticObj match { 64 | case obj: AbsObject[A, D] => 65 | obj.weakUpdateField(field -> v) 66 | case _ => 67 | val obj = new AbsObject[A, D] 68 | obj.updateField(field -> v) 69 | } 70 | map.update(staticAddr -> newStaticObj) 71 | case Param(i) => 72 | val addr = getParamAddr(i) 73 | map.update(addr -> v) 74 | } 75 | } 76 | def update(kv: (Loc, AbsValue[A, D])): This = { 77 | val newRawMap = updateRawMap(rawMap, kv) 78 | builder(newRawMap) 79 | } 80 | def get(k: Loc): AbsValue[A, D] = 81 | k match { 82 | case Local(id) => rawMap.get(AbsAddr(id)) 83 | case k: ArrayRef => getArray(k) 84 | case InstanceFieldRef(base, field) => 85 | val objRef = get(base) 86 | objRef match { 87 | case AbsRef(ids) => 88 | ids.foldLeft(AbsBottom: AbsValue[A, D]) { (v, id) => 89 | val obj = rawMap.get(AbsAddr(id)) 90 | val fieldValue = obj match { 91 | case obj: AbsObject[A, D] => obj.getField(field) 92 | case _ => AbsBottom 93 | } 94 | absValueOps.\/(v, fieldValue) 95 | } 96 | case _ => AbsBottom 97 | } 98 | case StaticFieldRef(clazz, field) => 99 | val staticAddr = getStaticAddr(clazz) 100 | val obj = rawMap.get(staticAddr) 101 | obj match { 102 | case obj: AbsObject[A, D] => obj.getField(field) 103 | case _ => AbsBottom 104 | } 105 | case Param(i) => 106 | val addr = getParamAddr(i) 107 | rawMap.get(addr) 108 | } 109 | def isStaticAddr(addr: AbsAddr): Boolean = 110 | addr.id.startsWith(StdObjectModel.staticPrefix) 111 | def isDynamicAddr(addr: AbsAddr): Boolean = 112 | addr.id.startsWith(StdObjectModel.dynamicPrefix) 113 | 114 | protected def builder( 115 | rawMap: MapDom[AbsAddr, GaloisIdentity[AbsValue[A, D]]] 116 | ): This 117 | } 118 | 119 | object StdObjectModel { 120 | val staticPrefix = "__static_obj_" 121 | val dynamicPrefix = "__dynamic_obj_" 122 | 123 | def getStaticAddr(className: ClassName): AbsAddr = 124 | AbsAddr(s"$staticPrefix${ClassName.toString(className)}") 125 | 126 | var addrIdx = 0 127 | var stmtToDyAddrNum = Map.empty[Stmt, Int] 128 | def getNewAddr(from: Stmt): AbsAddr = { 129 | val existingIdxOpt = stmtToDyAddrNum.get(from) 130 | val idx = 131 | if (existingIdxOpt.isEmpty) { 132 | addrIdx += 1 133 | stmtToDyAddrNum += from -> addrIdx 134 | addrIdx 135 | } else existingIdxOpt.get 136 | AbsAddr(s"$dynamicPrefix$idx") 137 | } 138 | def getParamAddr(i: Int): AbsAddr = AbsAddr(s"${dynamicPrefix}p$i") 139 | 140 | class AbsObject[A <: Galois: ArithmeticOps, D <: Galois: LatticeWithTopOps] 141 | extends AbsValue[A, D] { 142 | implicit val absValueOps = absValueOpsWithObject[A, D] 143 | 144 | protected[mem] var rawFieldMap = 145 | MapDom.empty[String, GaloisIdentity[AbsValue[A, D]]] 146 | 147 | def \/(that: AbsObject[A, D]): AbsObject[A, D] = { 148 | val newFieldMap = MapDom 149 | .ops[String, GaloisIdentity[AbsValue[A, D]]] 150 | .\/(rawFieldMap, that.rawFieldMap) 151 | val newObject = new AbsObject[A, D] 152 | newObject.rawFieldMap = newFieldMap 153 | newObject 154 | } 155 | 156 | def partialCompare(that: AbsObject[A, D]): Double = { 157 | MapDom 158 | .ops[String, GaloisIdentity[AbsValue[A, D]]] 159 | .partialCompare(rawFieldMap, that.rawFieldMap) 160 | } 161 | 162 | def widening(that: AbsObject[A, D])(implicit 163 | wideningAbsValue: Widening[GaloisIdentity[AbsValue[A, D]]] 164 | ): AbsObject[A, D] = { 165 | val newFieldMap = MapDom 166 | .widening[String, GaloisIdentity[AbsValue[A, D]]] 167 | .<>(rawFieldMap, that.rawFieldMap) 168 | val newObject = new AbsObject[A, D] 169 | newObject.rawFieldMap = newFieldMap 170 | newObject 171 | } 172 | 173 | def updateField(kv: (String, AbsValue[A, D])) = { 174 | val newFieldMap = rawFieldMap.update(kv) 175 | val newObject = new AbsObject[A, D] 176 | newObject.rawFieldMap = newFieldMap 177 | newObject 178 | } 179 | def weakUpdateField(kv: (String, AbsValue[A, D])) = { 180 | val newFieldMap = rawFieldMap.weakUpdate(kv) 181 | val newObject = new AbsObject[A, D] 182 | newObject.rawFieldMap = newFieldMap 183 | newObject 184 | } 185 | def getField(k: String): AbsValue[A, D] = rawFieldMap.get(k) 186 | } 187 | 188 | def absValueOpsWithObject[ 189 | A <: Galois: ArithmeticOps, 190 | D <: Galois: LatticeWithTopOps 191 | ] = 192 | new LatticeOps[GaloisIdentity[AbsValue[A, D]]] { 193 | val absValueOps = AbsValue.ops[A, D] 194 | override def \/( 195 | lhs: AbsValue[A, D], 196 | rhs: AbsValue[A, D] 197 | ): AbsValue[A, D] = { 198 | (lhs, rhs) match { 199 | case (x, y) 200 | if x.isInstanceOf[AbsObject[A, D]] && y 201 | .isInstanceOf[AbsObject[A, D]] => 202 | x.asInstanceOf[AbsObject[A, D]] \/ y.asInstanceOf[AbsObject[A, D]] 203 | case (_, _) => absValueOps.\/(lhs, rhs) 204 | } 205 | } 206 | override def partialCompare( 207 | lhs: AbsValue[A, D], 208 | rhs: AbsValue[A, D] 209 | ): Double = { 210 | (lhs, rhs) match { 211 | case (x, y) 212 | if x.isInstanceOf[AbsObject[A, D]] && y 213 | .isInstanceOf[AbsObject[A, D]] => 214 | x.asInstanceOf[AbsObject[A, D]] partialCompare y 215 | .asInstanceOf[AbsObject[A, D]] 216 | case (_, _) => absValueOps.partialCompare(lhs, rhs) 217 | } 218 | } 219 | override def bottom: AbsValue[A, D] = absValueOps.bottom 220 | } 221 | 222 | def wideningWithObject[A <: Galois: Widening, D <: Galois: Widening] = 223 | new Widening[GaloisIdentity[AbsValue[A, D]]] { 224 | implicit val wideningAbsValue = AbsValue.widening[A, D] 225 | override def <>(x: AbsValue[A, D], y: AbsValue[A, D]): AbsValue[A, D] = { 226 | (x, y) match { 227 | case (_, _) 228 | if x.isInstanceOf[AbsObject[A, D]] && y 229 | .isInstanceOf[AbsObject[A, D]] => 230 | x.asInstanceOf[AbsObject[A, D]] widening y 231 | .asInstanceOf[AbsObject[A, D]] 232 | case (_, _) => wideningAbsValue.<>(x, y) 233 | } 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/semantics/AbstractTransferable.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.semantics 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable.Context 5 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 6 | 7 | trait AbstractTransferable[D <: Galois] { 8 | def transfer(input: D#Abst, stmt: CoreStmt): D#Abst = { 9 | implicit val context = Context(stmt) 10 | stmt match { 11 | case s: Identity => transferIdentity(s, input) 12 | case s: Assign => transferAssign(s, input) 13 | case s: Invoke => transferInvoke(s, input) 14 | case s: If => transferIf(s, input) 15 | case s: Assume => transferAssume(s, input) 16 | case s: Return => transferReturn(s, input) 17 | case s: Nop => transferNop(s, input) 18 | case s: Goto => transferGoto(s, input) 19 | case s: EnterMonitor => transferEnterMonitor(s, input) 20 | case s: ExitMonitor => transferExitMonitor(s, input) 21 | case s: Throw => transferThrow(s, input) 22 | } 23 | } 24 | protected def transferIdentity(stmt: Identity, input: D#Abst)(implicit 25 | context: Context 26 | ): D#Abst = input 27 | protected def transferAssign(stmt: Assign, input: D#Abst)(implicit 28 | context: Context 29 | ): D#Abst = input 30 | protected def transferInvoke(stmt: Invoke, input: D#Abst)(implicit 31 | context: Context 32 | ): D#Abst = input 33 | protected def transferIf(stmt: If, input: D#Abst)(implicit 34 | context: Context 35 | ): D#Abst = input 36 | protected def transferAssume(stmt: Assume, input: D#Abst)(implicit 37 | context: Context 38 | ): D#Abst = input 39 | protected def transferReturn(stmt: Return, input: D#Abst)(implicit 40 | context: Context 41 | ): D#Abst = input 42 | protected def transferNop(stmt: Nop, input: D#Abst)(implicit 43 | context: Context 44 | ): D#Abst = input 45 | protected def transferGoto(stmt: Goto, input: D#Abst)(implicit 46 | context: Context 47 | ): D#Abst = input 48 | protected def transferEnterMonitor(stmt: EnterMonitor, input: D#Abst)(implicit 49 | context: Context 50 | ): D#Abst = input 51 | protected def transferExitMonitor(stmt: ExitMonitor, input: D#Abst)(implicit 52 | context: Context 53 | ): D#Abst = input 54 | protected def transferThrow(stmt: Throw, input: D#Abst)(implicit 55 | context: Context 56 | ): D#Abst = input 57 | } 58 | 59 | object AbstractTransferable { 60 | case class Context(stmt: Stmt) 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/semantics/Narrowing.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.semantics 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | 5 | trait Narrowing[D <: Galois] { 6 | def ><(x: D#Abst, y: D#Abst): D#Abst 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/semantics/StdSemantics.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.semantics 2 | 3 | import com.simplytyped.yoyak.framework.domain.{Galois, ArithmeticOps} 4 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 5 | import com.simplytyped.yoyak.framework.domain.mem.MemDomLike 6 | import com.simplytyped.yoyak.framework.domain.mem.MemElems.{ 7 | AbsRef, 8 | AbsTop, 9 | AbsArith, 10 | AbsValue 11 | } 12 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable.Context 13 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 14 | import com.simplytyped.yoyak.il.CommonIL.Value 15 | 16 | trait StdSemantics[A <: Galois, D, Mem <: MemDomLike[A, D, Mem]] 17 | extends AbstractTransferable[GaloisIdentity[Mem]] { 18 | val arithOps: ArithmeticOps[A] 19 | 20 | override protected def transferIdentity(stmt: Identity, input: Mem)(implicit 21 | context: Context 22 | ): Mem = { 23 | val (rv, output) = eval(stmt.rv, input) 24 | output.update(stmt.lv, rv) 25 | } 26 | override protected def transferAssign(stmt: Assign, input: Mem)(implicit 27 | context: Context 28 | ): Mem = { 29 | val (rv, output) = eval(stmt.rv, input) 30 | output.update(stmt.lv, rv) 31 | } 32 | override protected def transferInvoke(stmt: Invoke, input: Mem)(implicit 33 | context: Context 34 | ): Mem = input 35 | override protected def transferIf(stmt: If, input: Mem)(implicit 36 | context: Context 37 | ): Mem = input 38 | override protected def transferAssume(stmt: Assume, input: Mem)(implicit 39 | context: Context 40 | ): Mem = input 41 | override protected def transferReturn(stmt: Return, input: Mem)(implicit 42 | context: Context 43 | ): Mem = input 44 | override protected def transferNop(stmt: Nop, input: Mem)(implicit 45 | context: Context 46 | ): Mem = input 47 | override protected def transferGoto(stmt: Goto, input: Mem)(implicit 48 | context: Context 49 | ): Mem = input 50 | override protected def transferEnterMonitor(stmt: EnterMonitor, input: Mem)( 51 | implicit context: Context 52 | ): Mem = input 53 | override protected def transferExitMonitor(stmt: ExitMonitor, input: Mem)( 54 | implicit context: Context 55 | ): Mem = input 56 | override protected def transferThrow(stmt: Throw, input: Mem)(implicit 57 | context: Context 58 | ): Mem = input 59 | 60 | def eval(v: Value.t, input: Mem)(implicit 61 | context: Context 62 | ): (AbsValue[A, D], Mem) = { 63 | v match { 64 | case x: Value.Constant => evalConstant(x, input) 65 | case x: Value.Loc => evalLoc(x, input) 66 | case x: Value.BinExp => evalBinExp(x, input) 67 | case Value.This => (AbsRef(Set("$this")), input) 68 | case Value.CaughtExceptionRef => (AbsRef(Set("$caughtex")), input) 69 | case Value.CastExp(v, ofTy) => evalLoc(v, input) 70 | case Value.InstanceOfExp(v, ofTy) => (AbsTop, input) 71 | case Value.LengthExp(v) => (AbsTop, input) 72 | case Value.NewExp(ofTy) => input.alloc(context.stmt) 73 | case Value.NewArrayExp(ofTy, size) => input.alloc(context.stmt) 74 | } 75 | } 76 | protected def evalConstant(v: Value.Constant, input: Mem)(implicit 77 | context: Context 78 | ): (AbsValue[A, D], Mem) = 79 | (AbsArith[A](arithOps.lift(v)), input) 80 | 81 | protected def evalLoc(l: Value.Loc, input: Mem)(implicit 82 | context: Context 83 | ): (AbsValue[A, D], Mem) = 84 | (input.get(l), input) 85 | 86 | protected def evalBinExp(binExp: Value.BinExp, input: Mem)(implicit 87 | context: Context 88 | ): (AbsValue[A, D], Mem) = { 89 | binExp match { 90 | case e: Value.CompBinExp => evalCompBinExp(e, input) 91 | case e: Value.CondBinExp => evalCondBinExp(e, input) 92 | } 93 | } 94 | protected def evalCompBinExp(compExp: Value.CompBinExp, input: Mem)(implicit 95 | context: Context 96 | ): (AbsValue[A, D], Mem) = { 97 | val (lv, output1) = eval(compExp.lv, input) 98 | val (rv, output2) = eval(compExp.rv, output1) 99 | val result = (lv, rv) match { 100 | case (AbsArith(d1), AbsArith(d2)) => 101 | import Value.BinOp 102 | compExp.op match { 103 | case BinOp.+ => AbsArith(arithOps.+(d1, d2)) 104 | case BinOp.- => AbsArith(arithOps.-(d1, d2)) 105 | case BinOp.* => AbsArith(arithOps.*(d1, d2)) 106 | case BinOp./ => AbsArith(arithOps./(d1, d2)) 107 | case BinOp.&& => AbsTop 108 | case BinOp.|| => AbsTop 109 | case BinOp.& => AbsTop 110 | case BinOp.| => AbsTop 111 | case BinOp.>> => AbsTop 112 | case BinOp.<< => AbsTop 113 | case BinOp.>>> => AbsTop 114 | case BinOp.% => AbsTop 115 | case BinOp.^ => AbsTop 116 | case BinOp.cmp => AbsTop 117 | case BinOp.cmpl => AbsTop 118 | case BinOp.cmpg => AbsTop 119 | } 120 | case _ => AbsTop 121 | } 122 | (result, output2) 123 | } 124 | protected def evalCondBinExp(condExp: Value.CondBinExp, input: Mem)(implicit 125 | context: Context 126 | ): (AbsValue[A, D], Mem) = { 127 | (AbsTop, input) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/framework/semantics/Widening.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.semantics 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois 4 | 5 | trait Widening[D <: Galois] { 6 | def <>(x: D#Abst, y: D#Abst): D#Abst 7 | } 8 | 9 | object Widening { 10 | def NoWidening[A <: Galois]: Widening[A] = 11 | new Widening[A] { 12 | override def <>(x: A#Abst, y: A#Abst): A#Abst = y 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/EdgeLike.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | /** basic edge 4 | */ 5 | trait EdgeLike[Node <: NodeLike[Node]] { 6 | type L 7 | val label: L 8 | val from: Node 9 | val to: Node 10 | 11 | override def toString: String = { 12 | s"$from -> $to" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/GraphLike.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | /** very basic graph functionalities 4 | */ 5 | trait GraphLike[Node <: NodeLike[Node], Edge <: EdgeLike[ 6 | Node 7 | ], Graph <: GraphLike[Node, Edge, Graph]] { self: Graph => 8 | val edges: Set[Edge] 9 | val nodes: Set[Node] 10 | 11 | val nexts: Map[Node, Set[Edge]] 12 | val prevs: Map[Node, Set[Edge]] 13 | 14 | def newEdge(from: Node, to: Node): Edge 15 | 16 | def addEdge(from: Node, to: Node): Graph 17 | def addEdge(e: Edge): Graph 18 | def removeEdge(from: Node, to: Node): Graph 19 | def removeEdge(e: Edge): Graph 20 | def removeNode(n: Node): Graph 21 | def replaceNode(origNode: Node, newNode: Node): Graph 22 | def addNode(n: Node): Graph 23 | def getNexts(n: Node): Set[Node] 24 | def getPrevs(n: Node): Set[Node] 25 | 26 | def countNodes: Int = nodes.size 27 | def countEdges: Int = edges.size 28 | 29 | def ++(other: Graph): Graph = { 30 | val nodeAdded = other.nodes.foldLeft(self) { _.addNode(_) } 31 | other.edges.foldLeft(nodeAdded) { _.addEdge(_) } 32 | } 33 | 34 | override def toString: String = { 35 | val buf = new StringBuffer() 36 | buf.append("digraph yoyak {\n") 37 | buf.append(nodes.mkString(";\n")) 38 | buf.append(";\n") 39 | buf.append(edges.mkString(";\n")) 40 | buf.append(";\n}") 41 | buf.toString 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/ImmutableGraphLike.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | /** trait for immutable graphs 4 | */ 5 | trait ImmutableGraphLike[Node <: NodeLike[Node], Edge <: EdgeLike[ 6 | Node 7 | ], Graph <: GraphLike[Node, Edge, Graph]] 8 | extends GraphLike[Node, Edge, Graph] { self: Graph => 9 | def addEdge(from: Node, to: Node): Graph = { 10 | val newE = newEdge(from, to) 11 | addEdge(newE) 12 | } 13 | def addEdge(e: Edge): Graph = { 14 | val newNodes = nodes ++ Seq(e.from, e.to) 15 | val newEdges = edges + e 16 | val newNexts = nexts + (e.from -> (nexts.getOrElse(e.from, Set.empty) + e)) 17 | val newPrevs = prevs + (e.to -> (prevs.getOrElse(e.to, Set.empty) + e)) 18 | builder(newNodes, newEdges, newNexts, newPrevs) 19 | } 20 | def removeEdge(from: Node, to: Node): Graph = { 21 | val newE = newEdge(from, to) 22 | removeEdge(newE) 23 | } 24 | def removeEdge(e: Edge): Graph = { 25 | val newEdges = edges - e 26 | val newNextSet = nexts(e.from) - e 27 | val newNexts = 28 | if (newNextSet.isEmpty) nexts - e.from else nexts + (e.from -> newNextSet) 29 | val newPrevSet = prevs(e.to) - e 30 | val newPrevs = 31 | if (newPrevSet.isEmpty) prevs - e.to else prevs + (e.to -> newPrevSet) 32 | builder(nodes, newEdges, newNexts, newPrevs) 33 | } 34 | def removeNode(n: Node): Graph = { 35 | val incomingEdges = prevs.getOrElse(n, Set.empty[Edge]) 36 | val outgoingEdges = nexts.getOrElse(n, Set.empty[Edge]) 37 | (incomingEdges ++ outgoingEdges).foldLeft( 38 | builder(nodes - n, edges, nexts, prevs) 39 | ) { _.removeEdge(_) } 40 | } 41 | def replaceNode(origNode: Node, newNode: Node): Graph = { 42 | val nextEdges = getNexts(origNode).map { newEdge(newNode, _) } 43 | val prevsEdges = getPrevs(origNode).map { newEdge(_, newNode) } 44 | val removedGraph = removeNode(origNode) 45 | (nextEdges ++ prevsEdges).foldLeft(removedGraph) { _.addEdge(_) } 46 | } 47 | def addNode(n: Node): Graph = { 48 | val newNodes = nodes + n 49 | builder(newNodes, edges, nexts, prevs) 50 | } 51 | def getNexts(n: Node): Set[Node] = { 52 | nexts 53 | .get(n) 54 | .map { _.aggregate(Set.empty[Node])(_ + _.to, _ ++ _) } 55 | .getOrElse(Set.empty[Node]) 56 | } 57 | def getPrevs(n: Node): Set[Node] = { 58 | prevs 59 | .get(n) 60 | .map { _.aggregate(Set.empty[Node])(_ + _.from, _ ++ _) } 61 | .getOrElse(Set.empty[Node]) 62 | } 63 | 64 | def builder( 65 | nodes: Set[Node], 66 | edges: Set[Edge], 67 | nexts: Map[Node, Set[Edge]], 68 | prevs: Map[Node, Set[Edge]] 69 | ): Graph 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/NodeLike.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | /** basic node (vertex) 4 | */ 5 | trait NodeLike[Node <: NodeLike[Node]] { 6 | type D 7 | val data: D 8 | override def toString: String = { 9 | s"$data" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/algo/GraphRefactoring.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph.algo 2 | 3 | abstract class GraphRefactoring[G, N] { 4 | def mergePairedNodes(merger: (N, N) => N)(g: G): G 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/algo/GraphRefactoringImpl.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph.algo 2 | 3 | import com.simplytyped.yoyak.graph.{NodeLike, EdgeLike, GraphLike} 4 | 5 | trait GraphRefactoringImpl[Node <: NodeLike[Node], Edge <: EdgeLike[ 6 | Node 7 | ], Graph <: GraphLike[Node, Edge, Graph]] 8 | extends GraphRefactoring[Graph, Node] { 9 | def mergePairedNodes(merger: (Node, Node) => Node)(g: Graph): Graph = { 10 | def findMerged(remapper: Map[Node, Node])(n: Node): Node = { 11 | val resultOpt = remapper.get(n) 12 | if (resultOpt.isEmpty) n 13 | else if (resultOpt.get == n) n 14 | else findMerged(remapper)(resultOpt.get) 15 | } 16 | // find the edges which have a single next node 17 | val oneNexts = g.nexts.filter { _._2.size == 1 }.flatMap { _._2 } 18 | // and find the subset of the edges which also have a single prev node 19 | val pairedEdges = oneNexts.filter { x => g.getPrevs(x.to).size == 1 } 20 | val (_, finalG) = pairedEdges.foldLeft(Map.empty[Node, Node], g) { 21 | case ((remapper, g), e) => 22 | val to = findMerged(remapper)(e.to) 23 | val from = findMerged(remapper)(e.from) 24 | val newNode = merger(from, to) 25 | val newRemapper = remapper + (to -> newNode) + (from -> newNode) 26 | val outgoingNodesOfTwo = g.getNexts(to).map { findMerged(newRemapper) } 27 | val incomingNodesOfTwo = 28 | g.getPrevs(from).map { findMerged(newRemapper) } 29 | val vanillaG = g.removeNode(to).removeNode(from) 30 | val incomingAdded = incomingNodesOfTwo.foldLeft(vanillaG) { 31 | _.addEdge(_, newNode) 32 | } 33 | val newG = outgoingNodesOfTwo.foldLeft(incomingAdded) { 34 | _.addEdge(newNode, _) 35 | } 36 | (newRemapper, newG) 37 | } 38 | finalG 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/algo/GraphTraverse.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph.algo 2 | 3 | abstract class GraphTraverse[G, N] { 4 | def depthFirstTraverse(g: G): Stream[N] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/graph/algo/GraphTraverseImpl.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph.algo 2 | 3 | import com.simplytyped.yoyak.graph.{NodeLike, EdgeLike, GraphLike} 4 | 5 | trait GraphTraverseImpl[Node <: NodeLike[Node], Edge <: EdgeLike[ 6 | Node 7 | ], Graph <: GraphLike[Node, Edge, Graph]] 8 | extends GraphTraverse[Graph, Node] { 9 | def depthFirstTraverse(g: Graph): Stream[Node] = { 10 | def depthFirstTraverse_aux( 11 | visiting: Node, 12 | visited: Set[Node], 13 | notVisited: List[Node] 14 | ): Stream[Node] = { 15 | val nexts = g.getNexts(visiting).toList 16 | val newNotVisited = (nexts ++ notVisited).dropWhile(visited) 17 | if (newNotVisited.isEmpty) visiting #:: Stream.empty[Node] 18 | else 19 | visiting #:: depthFirstTraverse_aux( 20 | newNotVisited.head, 21 | visited + visiting, 22 | newNotVisited.tail 23 | ) 24 | } 25 | val roots = (g.nexts.keySet -- g.prevs.keySet).toList 26 | if (roots.isEmpty) Stream.empty[Node] 27 | else depthFirstTraverse_aux(roots.head, Set(), roots.tail) 28 | } 29 | def findLoopheads(g: Graph): Vector[Node] = { 30 | def findLoopheads_aux(visiting: Node, visited: Set[Node]): Vector[Node] = { 31 | val nexts = g.getNexts(visiting).toList 32 | nexts.foldLeft(Vector.empty[Node]) { case (l, n) => 33 | if (visited(n)) l :+ n 34 | else l ++ findLoopheads_aux(n, visited + visiting) 35 | } 36 | } 37 | val roots = (g.nexts.keySet -- g.prevs.keySet).toList 38 | if (roots.isEmpty) Vector.empty[Node] 39 | else 40 | roots 41 | .foldLeft(Vector.empty[Node]) { case (l, n) => 42 | l ++ findLoopheads_aux(n, Set()) 43 | } 44 | .distinct 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/Attachable.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il 2 | 3 | import com.simplytyped.yoyak.il.Position.NoSourceInfo 4 | 5 | trait Attachable { 6 | protected var sourcePos: Position = NoSourceInfo 7 | def setPos(pos: Position): this.type = { sourcePos = pos; this } 8 | def pos = sourcePos 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/CommonIL.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Statement.CoreStmt 4 | import com.simplytyped.yoyak.il.cfg.CFG 5 | 6 | /* container for common IL 7 | common IL is an input program representation before graph transformation 8 | - assign statement 9 | - invoke statement 10 | - if statement 11 | - block statement 12 | - return statement 13 | - nop statement 14 | - goto statement 15 | */ 16 | object CommonIL { 17 | case class Program( 18 | classes: Map[ClassName, Clazz] 19 | ) { 20 | lazy val methods = classes.values.foldLeft(Map.empty[MethodSig, Method]) { 21 | _ ++ _.methods 22 | } 23 | def findByMethodName(name: String): List[Method] = { 24 | var list = List.empty[Method] 25 | for ((_, clazz) <- classes; (_, method) <- clazz.methods) { 26 | if (method.name.methodName == name) list ::= method 27 | } 28 | list 29 | } 30 | } 31 | 32 | case class Clazz( 33 | name: ClassName, 34 | methods: Map[MethodSig, Method], 35 | interfaces: Set[ClassName], 36 | superClass: ClassName 37 | ) 38 | 39 | case class Method( 40 | name: MethodSig, 41 | statements: List[CoreStmt], 42 | var cfg: Option[CFG] = None 43 | ) 44 | 45 | case class ClassName(packageName: List[String], name: String) 46 | object ClassName { 47 | def apply(className: String): ClassName = { 48 | val clsName = 49 | if (className.startsWith("L") && className.endsWith(";")) { 50 | className.substring(1, className.length - 1).replace("/", ".") 51 | } else className 52 | val splitedName = clsName.split('.') 53 | if (splitedName.isEmpty) ClassName(List.empty, clsName) 54 | else ClassName(splitedName.dropRight(1).toList, splitedName.last) 55 | } 56 | def toString(className: ClassName): String = { 57 | if (className.packageName.nonEmpty) 58 | className.packageName.mkString(".") + "." + className.name 59 | else className.name 60 | } 61 | } 62 | 63 | case class MethodSig( 64 | className: ClassName, 65 | methodName: String, 66 | params: List[Type.ValueType] 67 | ) 68 | object MethodSig { 69 | val dummy = MethodSig(ClassName("dummy"), "dummy", List()) 70 | } 71 | 72 | object Statement { 73 | abstract class Stmt extends Attachable { 74 | override def equals(that: Any): Boolean = 75 | this eq that.asInstanceOf[AnyRef] 76 | override def hashCode(): Int = System.identityHashCode(this) 77 | 78 | private[Stmt] def copyAttr(stmt: Stmt): this.type = { 79 | sourcePos = stmt.pos; this 80 | } 81 | } 82 | 83 | case class Block(stmts: StatementContainer) extends Stmt 84 | 85 | case class Switch(v: Value.Loc, keys: List[Value.t], targets: List[Target]) 86 | extends Stmt 87 | 88 | case class Placeholder(x: AnyRef) extends Stmt { 89 | override def equals(that: Any): Boolean = { 90 | that match { 91 | case Placeholder(x2) => x eq x2 92 | case _ => false 93 | } 94 | } 95 | override def hashCode(): Int = System.identityHashCode(x) 96 | } 97 | 98 | sealed trait CoreStmt extends Stmt 99 | 100 | case class If(cond: Value.CondBinExp, target: Target) extends CoreStmt 101 | 102 | case class Goto(target: Target) extends CoreStmt 103 | 104 | sealed trait CfgStmt extends CoreStmt 105 | 106 | case class Identity(lv: Value.Local, rv: Value.Param) extends CfgStmt 107 | 108 | case class Assign(lv: Value.Loc, rv: Value.t) extends CfgStmt 109 | 110 | case class Invoke(ret: Option[Value.Local], callee: Type.InvokeType) 111 | extends CfgStmt 112 | 113 | case class Assume(cond: Value.CondBinExp) extends CfgStmt 114 | 115 | case class Return(v: Option[Value.Loc]) extends CfgStmt 116 | 117 | case class Nop() extends CfgStmt 118 | 119 | case class EnterMonitor(v: Value.Loc) extends CfgStmt 120 | 121 | case class ExitMonitor(v: Value.Loc) extends CfgStmt 122 | 123 | case class Throw(v: Value.Loc) extends CfgStmt 124 | 125 | class Target { 126 | private var elem: Stmt = _ 127 | def getStmt: Stmt = elem 128 | def setStmt(stmt: Stmt): Target = { elem = stmt; this } 129 | def map(f: Stmt => Stmt): Target = { elem = f(elem); this } 130 | } 131 | 132 | class StatementContainer { 133 | private var stmts: List[Stmt] = List.empty 134 | def getStmts: List[Stmt] = stmts 135 | def setStmts(s: List[Stmt]): StatementContainer = { stmts = s; this } 136 | def map(f: Stmt => Stmt): StatementContainer = { 137 | stmts = stmts.map { f }; this 138 | } 139 | } 140 | 141 | object Stmt { 142 | object StmtCopier { 143 | def Block(block: Block, stmts: StatementContainer) = 144 | new Block(stmts).copyAttr(block) 145 | 146 | def Switch( 147 | switch: Switch, 148 | v: Value.Loc, 149 | keys: List[Value.t], 150 | targets: List[Target] 151 | ) = 152 | new Switch(v, keys, targets).copyAttr(switch) 153 | 154 | def Placeholder(ph: Placeholder, x: AnyRef) = 155 | new Placeholder(x).copyAttr(ph) 156 | 157 | def If(i: If, cond: Value.CondBinExp, target: Target) = 158 | new If(cond, target).copyAttr(i) 159 | 160 | def Goto(goto: Goto, target: Target) = 161 | new Goto(target).copyAttr(goto) 162 | 163 | def Identity(iden: Identity, lv: Value.Local, rv: Value.Param) = 164 | new Identity(lv, rv).copyAttr(iden) 165 | 166 | def Assign(assign: Assign, lv: Value.Loc, rv: Value.t) = 167 | new Assign(lv, rv).copyAttr(assign) 168 | 169 | def Invoke( 170 | invoke: Invoke, 171 | ret: Option[Value.Local], 172 | callee: Type.InvokeType 173 | ) = 174 | new Invoke(ret, callee).copyAttr(invoke) 175 | 176 | def Assume(assume: Assume, cond: Value.CondBinExp) = 177 | new Assume(cond).copyAttr(assume) 178 | 179 | def Return(ret: Return, v: Option[Value.Loc]) = 180 | new Return(v).copyAttr(ret) 181 | 182 | def Nop(nop: Nop) = 183 | new Nop().copyAttr(nop) 184 | 185 | def EnterMonitor(mon: EnterMonitor, v: Value.Loc) = 186 | new EnterMonitor(v).copyAttr(mon) 187 | 188 | def ExitMonitor(mon: ExitMonitor, v: Value.Loc) = 189 | new ExitMonitor(v).copyAttr(mon) 190 | 191 | def Throw(thr: Throw, v: Value.Loc) = 192 | new Throw(v).copyAttr(thr) 193 | } 194 | def expandSwitch(switch: Switch): List[If] = { 195 | import Value.{CondBinExp, BinOp} 196 | switch.keys.zip(switch.targets).map { case (k, t) => 197 | If(CondBinExp(switch.v, BinOp.==, k), t).copyAttr(switch) 198 | } 199 | } 200 | } 201 | } 202 | 203 | object Type { 204 | sealed abstract class InvokeType { 205 | val callee: MethodSig 206 | val args: List[Value.Loc] 207 | } 208 | case class DynamicInvoke( 209 | callee: MethodSig, 210 | args: List[Value.Loc], 211 | base: Value.Loc 212 | ) extends InvokeType 213 | case class StaticInvoke(callee: MethodSig, args: List[Value.Loc]) 214 | extends InvokeType 215 | 216 | sealed abstract class ValueType 217 | 218 | sealed abstract class PrimType extends ValueType 219 | case object IntegerType extends PrimType 220 | case object LongType extends PrimType 221 | case object FloatType extends PrimType 222 | case object DoubleType extends PrimType 223 | case object CharType extends PrimType 224 | case object ByteType extends PrimType 225 | case object BooleanType extends PrimType 226 | case object ShortType extends PrimType 227 | case object NullType extends PrimType 228 | case object VoidType extends PrimType 229 | 230 | sealed abstract class ReferenceType extends ValueType 231 | case class RefType(className: ClassName) extends ReferenceType 232 | case class ArrayType(t: ValueType, dim: Int) extends ReferenceType 233 | 234 | case object UnknownType extends ValueType 235 | 236 | object CommonTypes { 237 | val String = RefType(ClassName("java.lang.String")) 238 | } 239 | def lub(ty1: ValueType, ty2: ValueType): ValueType = { 240 | (ty1, ty2) match { 241 | case (IntegerType, IntegerType) => IntegerType 242 | case (IntegerType, UnknownType) => IntegerType 243 | case (UnknownType, IntegerType) => IntegerType 244 | case (LongType, LongType) => LongType 245 | case (LongType, UnknownType) => LongType 246 | case (UnknownType, LongType) => LongType 247 | case (FloatType, FloatType) => FloatType 248 | case (FloatType, UnknownType) => FloatType 249 | case (UnknownType, FloatType) => FloatType 250 | case (DoubleType, DoubleType) => DoubleType 251 | case (DoubleType, UnknownType) => DoubleType 252 | case (UnknownType, DoubleType) => DoubleType 253 | case (CharType, CharType) => CharType 254 | case (CharType, UnknownType) => CharType 255 | case (UnknownType, CharType) => CharType 256 | case (ByteType, ByteType) => ByteType 257 | case (ByteType, UnknownType) => ByteType 258 | case (UnknownType, ByteType) => ByteType 259 | case (BooleanType, BooleanType) => BooleanType 260 | case (BooleanType, UnknownType) => BooleanType 261 | case (UnknownType, BooleanType) => BooleanType 262 | case (ShortType, ShortType) => ShortType 263 | case (ShortType, UnknownType) => ShortType 264 | case (UnknownType, ShortType) => ShortType 265 | case (NullType, x) => x 266 | case (x, NullType) => x 267 | case (VoidType, VoidType) => VoidType 268 | case (VoidType, UnknownType) => VoidType 269 | case (UnknownType, VoidType) => VoidType 270 | case (ref1 @ RefType(_), ref2 @ RefType(_)) => 271 | ref1 // TODO: need to consider class hierarchy 272 | case (ref @ RefType(_), UnknownType) => ref 273 | case (UnknownType, ref @ RefType(_)) => ref 274 | case (arr1 @ ArrayType(_, _), arr2 @ ArrayType(_, _)) => 275 | arr1 // TODO: need to consider class hierarchy 276 | case (arr @ ArrayType(_, _), UnknownType) => arr 277 | case (UnknownType, arr @ ArrayType(_, _)) => arr 278 | case _ => UnknownType 279 | } 280 | } 281 | } 282 | 283 | object Value { 284 | sealed abstract class t extends Typable with Attachable { 285 | private def copyAttr(value: t): this.type = { 286 | sourcePos = value.pos 287 | rawType = value.ty 288 | this 289 | } 290 | } 291 | 292 | sealed abstract class Instant extends t 293 | 294 | sealed abstract class Constant extends Instant 295 | case class IntegerConstant(v: Int) extends Constant { 296 | override final def ty = Type.IntegerType 297 | } 298 | case class LongConstant(v: Long) extends Constant { 299 | override final def ty = Type.LongType 300 | } 301 | case class FloatConstant(v: Float) extends Constant { 302 | override final def ty = Type.FloatType 303 | } 304 | case class DoubleConstant(v: Double) extends Constant { 305 | override final def ty = Type.DoubleType 306 | } 307 | case class CharConstant(v: Char) extends Constant { 308 | override final def ty = Type.CharType 309 | } 310 | case class ByteConstant(v: Byte) extends Constant { 311 | override final def ty = Type.ByteType 312 | } 313 | case class BooleanConstant(v: Boolean) extends Constant { 314 | override final def ty = Type.BooleanType 315 | } 316 | case class ShortConstant(v: Short) extends Constant { 317 | override final def ty = Type.ShortType 318 | } 319 | case class StringConstant(s: String) extends Constant { 320 | override final def ty = Type.CommonTypes.String 321 | } 322 | case class ClassConstant(refTy: Type.ReferenceType) extends Constant 323 | case object NullConstant extends Constant 324 | 325 | sealed abstract class Loc extends Instant 326 | case class Local(id: String) extends Loc 327 | case class ArrayRef(base: Loc, index: Instant) extends Loc 328 | case class InstanceFieldRef(base: Loc, field: String) extends Loc 329 | case class StaticFieldRef(clazz: ClassName, field: String) extends Loc 330 | case class Param(i: Int) extends Loc 331 | 332 | case object This extends t 333 | case object CaughtExceptionRef extends t 334 | 335 | case class CastExp(v: Loc, ofTy: Type.ValueType) extends t 336 | case class InstanceOfExp(v: Loc, ofTy: Type.ReferenceType) extends t 337 | case class LengthExp(v: Loc) extends t 338 | 339 | case class NewExp(ofTy: Type.RefType) extends t 340 | case class NewArrayExp(ofTy: Type.ValueType, size: Instant) extends t 341 | 342 | sealed abstract class BinExp extends t { 343 | val lv: Value.t 344 | val rv: Value.t 345 | val op: BinOp.Op 346 | } 347 | case class CompBinExp(lv: Value.t, op: BinOp.CompOp, rv: Value.t) 348 | extends BinExp 349 | case class CondBinExp(lv: Value.t, op: BinOp.CondOp, rv: Value.t) 350 | extends BinExp { 351 | def negate: CondBinExp = { 352 | val newOp = op match { 353 | case BinOp.< => BinOp.>= 354 | case BinOp.<= => BinOp.> 355 | case BinOp.> => BinOp.<= 356 | case BinOp.>= => BinOp.< 357 | case BinOp.== => BinOp.!= 358 | case BinOp.!= => BinOp.== 359 | } 360 | this.copy(op = newOp) 361 | } 362 | } 363 | 364 | object BinOp { 365 | sealed abstract class Op 366 | 367 | sealed abstract class CondOp extends Op 368 | case object < extends CondOp 369 | case object <= extends CondOp 370 | case object > extends CondOp 371 | case object >= extends CondOp 372 | case object == extends CondOp 373 | case object != extends CondOp 374 | 375 | sealed abstract class CompOp extends Op 376 | case object + extends CompOp 377 | case object - extends CompOp 378 | case object * extends CompOp 379 | case object / extends CompOp 380 | case object && extends CompOp 381 | case object || extends CompOp 382 | case object & extends CompOp 383 | case object | extends CompOp 384 | case object >> extends CompOp 385 | case object << extends CompOp 386 | case object >>> extends CompOp 387 | case object % extends CompOp 388 | case object ^ extends CompOp 389 | case object cmp extends CompOp 390 | case object cmpl extends CompOp 391 | case object cmpg extends CompOp 392 | } 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/CommonILHelper.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Statement.Stmt.StmtCopier 4 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 5 | import com.simplytyped.yoyak.il.CommonIL.Type._ 6 | import com.simplytyped.yoyak.il.CommonIL.Value 7 | import com.simplytyped.yoyak.il.CommonIL.Value._ 8 | 9 | object CommonILHelper { 10 | def getUnitSizeOf(ty: ValueType): Int = { 11 | ty match { 12 | case IntegerType => 1 13 | case LongType => 2 14 | case FloatType => 1 15 | case DoubleType => 2 16 | case CharType => 1 17 | case ByteType => 1 18 | case BooleanType => 1 19 | case ShortType => 1 20 | case NullType => 1 21 | case VoidType => 1 22 | 23 | case _: RefType => 1 24 | case _: ArrayType => 1 25 | case UnknownType => 1 26 | } 27 | } 28 | def stmtSubstitute(map: Map[Stmt, Stmt])(stmts: List[Stmt]): List[Stmt] = { 29 | def substitute(stmt: Stmt): Stmt = { 30 | val srcOpt = map.get(stmt) 31 | if (srcOpt.nonEmpty) srcOpt.get 32 | else { 33 | stmt match { 34 | case block: Block => 35 | block.stmts.map { substitute } 36 | case switch: Switch => 37 | switch.targets.map { _.map { substitute } } 38 | case iff: If => 39 | iff.target.map { substitute } 40 | case goto: Goto => 41 | goto.target.map { substitute } 42 | case _ => // do nothing 43 | } 44 | stmt 45 | } 46 | } 47 | stmts.map { substitute } 48 | } 49 | def stmtSubstituteCore( 50 | map: Map[Stmt, CoreStmt] 51 | )(stmts: List[CoreStmt]): List[CoreStmt] = { 52 | stmtSubstitute(map)(stmts).asInstanceOf[List[CoreStmt]] 53 | } 54 | def expandStmts(stmts: List[Stmt]): List[CoreStmt] = { 55 | var map = Map.empty[Stmt, CoreStmt] 56 | val resultStmts = stmts.flatMap { 57 | case block @ Block(innerStmts) => 58 | val output = expandStmts(innerStmts.getStmts) 59 | map += (block -> output.head) 60 | output 61 | case switch: Switch => 62 | val output = Stmt.expandSwitch(switch) 63 | map += (switch -> output.head) 64 | output 65 | case x: CoreStmt => List(x) 66 | } 67 | stmtSubstituteCore(map)(resultStmts) 68 | } 69 | private def substituteI( 70 | f: Value.Local => Value.Local 71 | )(v: Value.Instant): Value.Instant = { 72 | v match { 73 | case v: Loc => substituteL(f)(v) 74 | case _ => v 75 | } 76 | } 77 | private def substituteV( 78 | f: Value.Local => Value.Local 79 | )(v: Value.t): Value.t = { 80 | v match { 81 | case v: CastExp => v.copy(v = substituteL(f)(v.v)) 82 | case v: InstanceOfExp => v.copy(v = substituteL(f)(v.v)) 83 | case v: LengthExp => v.copy(v = substituteL(f)(v.v)) 84 | case v: NewArrayExp => v.copy(size = substituteI(f)(v.size)) 85 | case v: Instant => substituteI(f)(v) 86 | case v: CondBinExp => 87 | v.copy(lv = substituteV(f)(v.lv), rv = substituteV(f)(v.rv)) 88 | case v: CompBinExp => 89 | v.copy(lv = substituteV(f)(v.lv), rv = substituteV(f)(v.rv)) 90 | case _ => v 91 | } 92 | } 93 | private def substituteL( 94 | f: Value.Local => Value.Local 95 | )(v: Value.Loc): Value.Loc = { 96 | v match { 97 | case v: Local => f(v) 98 | case v: ArrayRef => 99 | v.copy(base = substituteL(f)(v.base), index = substituteI(f)(v.index)) 100 | case v: InstanceFieldRef => v.copy(base = substituteL(f)(v.base)) 101 | case v: StaticFieldRef => v 102 | case v: Param => v 103 | } 104 | } 105 | def substituteLocalDef( 106 | f: Value.Local => Value.Local 107 | )(stmt: CoreStmt): CoreStmt = { 108 | stmt match { 109 | case stmt @ Identity(lv, rv) => 110 | StmtCopier.Identity(stmt, f(lv), rv) 111 | case stmt @ Assign(lv, rv) => 112 | val newLv = 113 | if (lv.isInstanceOf[Local]) f(lv.asInstanceOf[Local]) else lv 114 | StmtCopier.Assign(stmt, newLv, rv) 115 | case stmt @ Invoke(ret, callee) => 116 | val newRet = ret.map { f } 117 | StmtCopier.Invoke(stmt, newRet, callee) 118 | case _ => stmt 119 | } 120 | } 121 | def substituteLocalUse( 122 | f: Value.Local => Value.Local 123 | )(stmt: CoreStmt): CoreStmt = { 124 | stmt match { 125 | case stmt @ If(CondBinExp(v1, op, v2), target) => 126 | val newCond = CondBinExp(substituteV(f)(v1), op, substituteV(f)(v2)) 127 | StmtCopier.If(stmt, newCond, target) 128 | case stmt @ Goto(target) => stmt 129 | case stmt @ Identity(lv, rv) => stmt 130 | case stmt @ Assign(lv, rv) => 131 | val newLv = if (lv.isInstanceOf[Local]) lv else substituteL(f)(lv) 132 | StmtCopier.Assign(stmt, newLv, substituteV(f)(rv)) 133 | case stmt @ Invoke(ret, callee) => 134 | val newCallee = callee match { 135 | case inv: DynamicInvoke => 136 | inv.copy( 137 | args = inv.args.map { substituteL(f) }, 138 | base = substituteL(f)(inv.base) 139 | ) 140 | case inv: StaticInvoke => 141 | inv.copy(args = inv.args.map { substituteL(f) }) 142 | } 143 | StmtCopier.Invoke(stmt, ret, newCallee) 144 | case stmt @ Assume(CondBinExp(v1, op, v2)) => 145 | val newCond = CondBinExp(substituteV(f)(v1), op, substituteV(f)(v2)) 146 | StmtCopier.Assume(stmt, newCond) 147 | case stmt @ Return(v) => 148 | StmtCopier.Return(stmt, v.map { substituteL(f) }) 149 | case stmt @ Nop() => stmt 150 | case stmt @ EnterMonitor(v) => 151 | StmtCopier.EnterMonitor(stmt, substituteL(f)(v)) 152 | case stmt @ ExitMonitor(v) => 153 | StmtCopier.ExitMonitor(stmt, substituteL(f)(v)) 154 | case stmt @ Throw(v) => 155 | StmtCopier.Throw(stmt, substituteL(f)(v)) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/PrettyPrinter.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il 2 | 3 | import com.simplytyped.yoyak.il.CommonIL._ 4 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 5 | import com.simplytyped.yoyak.il.CommonIL.Value._ 6 | import com.simplytyped.yoyak.il.CommonIL.Type._ 7 | import com.simplytyped.yoyak.il.cfg.BasicBlock.{ 8 | ExitMarkerContainer, 9 | EntryMarkerContainer, 10 | CoreStatementContainer 11 | } 12 | import com.simplytyped.yoyak.il.cfg.{BasicEdge, BasicBlock, CFG} 13 | 14 | class PrettyPrinter { 15 | def toDot(cfg: CFG): String = { 16 | val buf = new StringBuffer() 17 | buf.append("digraph yoyak {\n") 18 | buf.append(cfg.nodes.map { toString }.mkString(";\n")) 19 | buf.append(";\n") 20 | buf.append(cfg.edges.map { toString }.mkString(";\n")) 21 | buf.append(";\n}") 22 | buf.toString 23 | } 24 | def toString(node: BasicBlock): String = { 25 | node.data match { 26 | case _: CoreStatementContainer => 27 | "\"" + node.id + ":\\n" + node.data.getStmts 28 | .map { toString } 29 | .map { _.replace("\"", "'") } 30 | .mkString("\\n") + "\"" 31 | case EntryMarkerContainer => "\"ENTRY\"" 32 | case ExitMarkerContainer => "\"EXIT\"" 33 | } 34 | 35 | } 36 | def toString(edge: BasicEdge): String = { 37 | s"${toString(edge.from)} -> ${toString(edge.to)}" 38 | } 39 | def toString(pgm: Program): String = { 40 | pgm.classes.map { c => toString(c._2) }.mkString("\n\n") 41 | } 42 | def toString(clazz: Clazz): String = { 43 | clazz.methods.map { m => toString(m._2) }.mkString("\n\n") 44 | } 45 | def toString(method: Method): String = { 46 | s"${toString(method.name)}\n${method.statements.zipWithIndex 47 | .map { s => s"${s._2} ${toString(s._1)}" } 48 | .mkString("\n")}" 49 | } 50 | def toString(sig: MethodSig): String = { 51 | s"${ClassName.toString(sig.className)}.${sig.methodName}(${sig.params.map { toString }.mkString(",")})" 52 | } 53 | def toString(stmt: Stmt): String = { 54 | stmt match { 55 | case Block(stmts) => stmts.getStmts.map { toString }.mkString("\n") 56 | 57 | case Switch(v, keys, targets) => 58 | s"switch(${toString(v)})\n${keys 59 | .zip(targets) 60 | .map { case (k, goto) => s"case ${toString(k)} => goto $goto" } 61 | .mkString("\n")}" 62 | 63 | case Identity(lv, rv) => s"${toString(lv)} := ${toString(rv)}" 64 | 65 | case Assign(lv, rv) => s"${toString(lv)} = ${toString(rv)}" 66 | 67 | case Invoke(ret, callee) => 68 | callee match { 69 | case DynamicInvoke(sig, args, base) => 70 | s"${ret.map { x => s"${toString(x)} = " }.getOrElse("")}${toString( 71 | base 72 | )}.${toString(sig)}(${args.map { toString }.mkString(",")})" 73 | case StaticInvoke(sig, args) => 74 | s"${ret.map { x => s"${toString(x)} = " }.getOrElse("")}${toString( 75 | sig 76 | )}(${args.map { toString }.mkString(",")})" 77 | } 78 | 79 | case If(cond, target) => s"if(${toString(cond)}) goto $target" 80 | 81 | case Assume(cond) => s"assume(${toString(cond)})" 82 | 83 | case Return(v) => s"return ${v.map { toString }.getOrElse("")}" 84 | 85 | case Nop() => "nop" 86 | 87 | case Goto(target) => s"goto $target" 88 | 89 | case EnterMonitor(v) => s"enterMonitor(${toString(v)})" 90 | 91 | case ExitMonitor(v) => s"exitMonitor(${toString(v)})" 92 | 93 | case Throw(v) => s"throw ${toString(v)}" 94 | } 95 | } 96 | def toString(va: Value.t): String = { 97 | va match { 98 | case IntegerConstant(v) => v.toString 99 | case LongConstant(v) => v.toString 100 | case FloatConstant(v) => v.toString 101 | case DoubleConstant(v) => v.toString 102 | case CharConstant(v) => v.toString 103 | case ByteConstant(v) => v.toString 104 | case BooleanConstant(v) => v.toString 105 | case ShortConstant(v) => v.toString 106 | case StringConstant(s) => "\"" + s + "\"" 107 | case ClassConstant(refTy) => s"${toString(refTy)}.class" 108 | case NullConstant => "null" 109 | 110 | case Local(id) => s"$id:${toString(va.ty)}" 111 | case ArrayRef(base, index) => s"${toString(base)}[${toString(index)}]" 112 | case InstanceFieldRef(base, field) => s"${toString(base)}.$field" 113 | case StaticFieldRef(clazz, field) => 114 | s"${ClassName.toString(clazz)}.$field" 115 | 116 | case This => "$this" 117 | case CaughtExceptionRef => "$exception" 118 | case Param(i) => "$param" + i 119 | 120 | case CastExp(v, ty) => s"(${toString(ty)}) ${toString(v)}" 121 | case InstanceOfExp(v, ty) => s"${toString(v)} instanceof ${toString(ty)}" 122 | case LengthExp(v) => s"lengthof ${toString(v)}" 123 | 124 | case NewExp(ty) => s"new ${toString(ty)}" 125 | case NewArrayExp(ty, size) => s"new ${toString(ty)}[${toString(size)}]" 126 | 127 | case CompBinExp(lv, op, rv) => 128 | s"${toString(lv)} ${toString(op)} ${toString(rv)}" 129 | case CondBinExp(lv, op, rv) => 130 | s"${toString(lv)} ${toString(op)} ${toString(rv)}" 131 | } 132 | } 133 | def toString(op: BinOp.Op): String = op.toString 134 | def toString(ty: Type.ValueType): String = { 135 | ty match { 136 | case IntegerType => "int" 137 | case LongType => "long" 138 | case FloatType => "float" 139 | case DoubleType => "double" 140 | case CharType => "char" 141 | case ByteType => "byte" 142 | case BooleanType => "boolean" 143 | case ShortType => "short" 144 | case NullType => "null" 145 | case VoidType => "void" 146 | 147 | case RefType(className: ClassName) => ClassName.toString(className) 148 | case ArrayType(t: ValueType, dim: Int) => s"${toString(t)}[$dim]" 149 | case UnknownType => "unknown" 150 | } 151 | } 152 | } 153 | 154 | object PrettyPrinter { 155 | def print(pgm: Program) = { 156 | val printer = new PrettyPrinter 157 | println(printer.toString(pgm)) 158 | } 159 | def printByMethodName(name: String, pgm: Program) = { 160 | val printer = new PrettyPrinter 161 | pgm.findByMethodName(name).foreach { m => println(printer.toString(m)) } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/SourceInfo.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il 2 | 3 | trait Position { 4 | var startLine: Int 5 | var endLine: Int 6 | var startColumn: Int 7 | var endColumn: Int 8 | var fileName: String 9 | } 10 | 11 | object Position { 12 | 13 | class SourceInfo( 14 | var startLine: Int, 15 | var endLine: Int, 16 | var startColumn: Int, 17 | var endColumn: Int, 18 | var fileName: String 19 | ) extends Position { 20 | override def toString: String = 21 | s"$startLine:$endLine:$startColumn:$endColumn:$fileName" 22 | } 23 | 24 | object NoSourceInfo extends Position { 25 | var startLine: Int = 0 26 | var endLine: Int = 0 27 | var startColumn: Int = 0 28 | var endColumn: Int = 0 29 | var fileName: String = "N/A" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/Typable.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Type 4 | 5 | class Typable { 6 | protected var rawType: Type.ValueType = Type.UnknownType 7 | 8 | def setType(ty: Type.ValueType): this.type = { rawType = ty; this } 9 | def ty = rawType 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/cfg/BasicBlock.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.cfg 2 | 3 | import com.simplytyped.yoyak.graph.NodeLike 4 | import com.simplytyped.yoyak.il.CommonIL.MethodSig 5 | import com.simplytyped.yoyak.il.CommonIL.Statement.{Nop, CoreStmt} 6 | import com.simplytyped.yoyak.il.cfg.BasicBlock.{ 7 | ExitMarkerContainer, 8 | EntryMarkerContainer, 9 | AbstractStatementContainer 10 | } 11 | 12 | case class BasicBlock(data: AbstractStatementContainer, id: Int) 13 | extends NodeLike[BasicBlock] { 14 | type D = AbstractStatementContainer 15 | override def toString = s"BasicBlock($id)" 16 | def isEntry: Boolean = data == EntryMarkerContainer 17 | def isExit: Boolean = data == ExitMarkerContainer 18 | } 19 | 20 | object BasicBlock { 21 | private var idCounter = 0 22 | private var idToMethodSigMap = Map.empty[Int, MethodSig] 23 | abstract class AbstractStatementContainer { 24 | def getStmts: List[CoreStmt] 25 | def setStmts(s: List[CoreStmt]): AbstractStatementContainer 26 | } 27 | object EntryMarkerContainer extends AbstractStatementContainer { 28 | def getStmts: List[CoreStmt] = List(Nop()) 29 | def setStmts(s: List[CoreStmt]): AbstractStatementContainer = this 30 | } 31 | object ExitMarkerContainer extends AbstractStatementContainer { 32 | def getStmts: List[CoreStmt] = List(Nop()) 33 | def setStmts(s: List[CoreStmt]): AbstractStatementContainer = this 34 | } 35 | class CoreStatementContainer extends AbstractStatementContainer { 36 | private var stmts: List[CoreStmt] = List.empty 37 | def getStmts = stmts 38 | def setStmts(s: List[CoreStmt]) = { stmts = s; this } 39 | } 40 | def apply(methodSig: MethodSig)(stmts: List[CoreStmt]): BasicBlock = { 41 | idCounter += 1 42 | idToMethodSigMap += idCounter -> methodSig 43 | BasicBlock(new CoreStatementContainer().setStmts(stmts), idCounter) 44 | } 45 | def getEntryBlock(): BasicBlock = { 46 | idCounter += 1 47 | BasicBlock(EntryMarkerContainer, idCounter) 48 | } 49 | def getExitBlock(): BasicBlock = { 50 | idCounter += 1 51 | BasicBlock(ExitMarkerContainer, idCounter) 52 | } 53 | def getMethodSig(bb: BasicBlock): Option[MethodSig] = 54 | idToMethodSigMap.get(bb.id) 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/cfg/BasicEdge.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.cfg 2 | 3 | import com.simplytyped.yoyak.graph.EdgeLike 4 | import com.simplytyped.yoyak.il.CommonIL.Value 5 | import com.simplytyped.yoyak.il.cfg.BasicEdge.{IntraEdge, EdgeType} 6 | 7 | case class BasicEdge( 8 | from: BasicBlock, 9 | to: BasicBlock, 10 | label: EdgeType = IntraEdge 11 | ) extends EdgeLike[BasicBlock] { 12 | type L = EdgeType 13 | } 14 | 15 | object BasicEdge { 16 | abstract class EdgeType 17 | case class InterEdge(retVar: Option[Value.Local]) extends EdgeType 18 | case object IntraEdge extends EdgeType 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/cfg/CFG.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.cfg 2 | 3 | import com.simplytyped.yoyak.graph.ImmutableGraphLike 4 | import com.simplytyped.yoyak.graph.algo.GraphTraverseImpl 5 | 6 | case class CFG( 7 | nodes: Set[BasicBlock], 8 | edges: Set[BasicEdge], 9 | nexts: Map[BasicBlock, Set[BasicEdge]], 10 | prevs: Map[BasicBlock, Set[BasicEdge]] 11 | ) extends ImmutableGraphLike[BasicBlock, BasicEdge, CFG] { 12 | def newEdge(from: BasicBlock, to: BasicBlock): BasicEdge = BasicEdge(from, to) 13 | def builder( 14 | nodes: Set[BasicBlock], 15 | edges: Set[BasicEdge], 16 | nexts: Map[BasicBlock, Set[BasicEdge]], 17 | prevs: Map[BasicBlock, Set[BasicEdge]] 18 | ): CFG = { 19 | CFG(nodes, edges, nexts, prevs) 20 | } 21 | def getEntry: Option[BasicBlock] = nodes.find { _.isEntry } 22 | def getExit: Option[BasicBlock] = nodes.find { _.isExit } 23 | def findBasicBlockByLineNumber(lineNumber: Int): Option[BasicBlock] = { 24 | nodes.find { _.data.getStmts.exists { _.pos.startLine == lineNumber } } 25 | } 26 | } 27 | 28 | object CFG { 29 | val empty = CFG(Set.empty, Set.empty, Map.empty, Map.empty) 30 | val traverse = new GraphTraverseImpl[BasicBlock, BasicEdge, CFG] {} 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/cfg/CommonILToCFG.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.cfg 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.{MethodSig, Method} 4 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 5 | 6 | import scala.collection.mutable.ListBuffer 7 | 8 | class CommonILToCFG { 9 | def makeBasicBlocks( 10 | methodSig: MethodSig, 11 | stmts: List[CoreStmt] 12 | ): List[BasicBlock] = { 13 | def sliceAtFirstBranch( 14 | stmts: List[CoreStmt] 15 | ): (List[CoreStmt], Option[Stmt], List[CoreStmt]) = { 16 | val idxOfFirstBranch = stmts.indexWhere { 17 | case _: If => true 18 | case _: Goto => true 19 | case _: Invoke => true 20 | case _: Return => true 21 | case _ => false 22 | } 23 | if (idxOfFirstBranch == -1) (stmts, None, List.empty[CoreStmt]) 24 | else { 25 | val (firstStmts, secondStmts) = stmts.splitAt(idxOfFirstBranch + 1) 26 | val target = firstStmts.last match { 27 | case If(_, t) => Some(t.getStmt) 28 | case Goto(t) => Some(t.getStmt) 29 | case _: Invoke => None 30 | case _: Return => None 31 | } 32 | (firstStmts, target, secondStmts) 33 | } 34 | } 35 | 36 | val output = new ListBuffer[List[CoreStmt]] 37 | var targetSet = Set.empty[Stmt] 38 | 39 | // split branch sources 40 | { 41 | var input = stmts 42 | while (input.nonEmpty) { 43 | val (slice, targetOpt, remaining) = sliceAtFirstBranch(input) 44 | output.append(slice) 45 | if (targetOpt.nonEmpty) targetSet += targetOpt.get 46 | input = remaining 47 | } 48 | } 49 | 50 | // split branch targets 51 | val finalOutput = output.flatMap { slice => 52 | var input = slice 53 | val buf = new ListBuffer[List[CoreStmt]] 54 | while (input.nonEmpty) { 55 | val idx = input.indexWhere(targetSet.apply, 1) 56 | if (idx == -1 || idx == 0) { 57 | buf.append(input) 58 | input = List.empty 59 | } else { 60 | val (first, second) = input.splitAt(idx) 61 | buf.append(first) 62 | input = second 63 | } 64 | } 65 | buf.toList 66 | } 67 | finalOutput.map { BasicBlock.apply(methodSig) }.toList 68 | } 69 | def insertAssume(cfg: CFG): CFG = { 70 | cfg.nodes.foreach { node => 71 | node.data.getStmts.last match { 72 | case If(cond, target) => 73 | val nexts = cfg.getNexts(node) 74 | if (nexts.size == 2) { 75 | nexts.foreach { n => 76 | if (n.data.getStmts.find { _ == target.getStmt }.nonEmpty) { 77 | val newStmts = Assume(cond) :: n.data.getStmts 78 | n.data.setStmts(newStmts) 79 | } else { 80 | val newStmts = Assume(cond.negate) :: n.data.getStmts 81 | n.data.setStmts(newStmts) 82 | } 83 | } 84 | } 85 | case _ => // do nothing 86 | } 87 | } 88 | cfg 89 | } 90 | def removeIfandGoto(cfg: CFG): CFG = { 91 | cfg.nodes.foreach { node => 92 | node.data.getStmts.last match { 93 | case _: If => 94 | val newStmts = 95 | if (node.data.getStmts.size > 1) node.data.getStmts.dropRight(1) 96 | else List(Nop()) 97 | node.data.setStmts(newStmts) 98 | case _: Goto => 99 | val newStmts = 100 | if (node.data.getStmts.size > 1) node.data.getStmts.dropRight(1) 101 | else List(Nop()) 102 | node.data.setStmts(newStmts) 103 | case _ => // do nothing 104 | } 105 | } 106 | cfg 107 | } 108 | def transform(method: Method): CFG = { 109 | def findByFirstStmt( 110 | blocks: List[BasicBlock], 111 | stmt: Stmt 112 | ): Option[BasicBlock] = blocks.find { _.data.getStmts.head == stmt } 113 | val entryBlock = BasicBlock.getEntryBlock() 114 | val basicBlocks = 115 | entryBlock :: makeBasicBlocks(method.name, method.statements) 116 | val exitBlock = BasicBlock.getExitBlock() 117 | val edges = basicBlocks.sliding(2).foldLeft(List.empty[BasicEdge]) { 118 | (edgeList, blockOfTwo) => 119 | blockOfTwo.head.data.getStmts.last match { 120 | case If(_, target) => 121 | val targetBlock = findByFirstStmt(basicBlocks, target.getStmt).get 122 | if (blockOfTwo.size == 1) 123 | BasicEdge( 124 | blockOfTwo.head, 125 | targetBlock 126 | ) :: edgeList // XXX: this case shouldn't happen 127 | else 128 | BasicEdge(blockOfTwo.head, blockOfTwo.last) :: BasicEdge( 129 | blockOfTwo.head, 130 | targetBlock 131 | ) :: edgeList 132 | case Goto(target) => 133 | val targetBlock = findByFirstStmt(basicBlocks, target.getStmt).get 134 | BasicEdge(blockOfTwo.head, targetBlock) :: edgeList 135 | case Return(_) => BasicEdge(blockOfTwo.head, exitBlock) :: edgeList 136 | case Throw(_) => BasicEdge(blockOfTwo.head, exitBlock) :: edgeList 137 | case _ => 138 | if (blockOfTwo.size == 1) edgeList 139 | else BasicEdge(blockOfTwo.head, blockOfTwo.last) :: edgeList 140 | } 141 | } 142 | val newCfg = CFG.empty 143 | val rawCfg = edges.foldLeft(newCfg) { _.addEdge(_) } 144 | (insertAssume _ andThen removeIfandGoto) apply rawCfg 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/hierarchy/ClassHierarchy.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.hierarchy 2 | 3 | import com.simplytyped.yoyak.graph.{ImmutableGraphLike, NodeLike, EdgeLike} 4 | import com.simplytyped.yoyak.il.CommonIL.ClassName 5 | import com.simplytyped.yoyak.il.hierarchy.ClassHierarchy.{ 6 | Inheritance, 7 | TypeDef, 8 | HierarchyGraph 9 | } 10 | 11 | class ClassHierarchy { 12 | private var graph = HierarchyGraph.empty 13 | 14 | def extendsFrom( 15 | childClass: ClassName, 16 | parentClass: ClassName 17 | ): ClassHierarchy = { 18 | graph = graph.addEdge(TypeDef(childClass), TypeDef(parentClass)) 19 | this 20 | } 21 | 22 | def implementWith( 23 | childClass: ClassName, 24 | superInterface: ClassName 25 | ): ClassHierarchy = { 26 | graph = graph.addEdge(TypeDef(childClass), TypeDef(superInterface)) 27 | this 28 | } 29 | 30 | def isExtendsFrom(childClass: ClassName, parentClass: ClassName): Boolean = { 31 | graph.edges(Inheritance(TypeDef(childClass), TypeDef(parentClass))) 32 | } 33 | 34 | def isImplementWith( 35 | childClass: ClassName, 36 | superInterface: ClassName 37 | ): Boolean = { 38 | graph.edges(Inheritance(TypeDef(childClass), TypeDef(superInterface))) 39 | } 40 | 41 | override def toString: String = graph.toString 42 | } 43 | 44 | object ClassHierarchy { 45 | case class HierarchyGraph( 46 | nodes: Set[TypeDef], 47 | edges: Set[Inheritance], 48 | nexts: Map[TypeDef, Set[Inheritance]], 49 | prevs: Map[TypeDef, Set[Inheritance]] 50 | ) extends ImmutableGraphLike[TypeDef, Inheritance, HierarchyGraph] { 51 | def newEdge(from: TypeDef, to: TypeDef): Inheritance = Inheritance(from, to) 52 | def builder( 53 | nodes: Set[TypeDef], 54 | edges: Set[Inheritance], 55 | nexts: Map[TypeDef, Set[Inheritance]], 56 | prevs: Map[TypeDef, Set[Inheritance]] 57 | ): HierarchyGraph = { 58 | HierarchyGraph(nodes, edges, nexts, prevs) 59 | } 60 | } 61 | object HierarchyGraph { 62 | val empty = HierarchyGraph(Set.empty, Set.empty, Map.empty, Map.empty) 63 | } 64 | 65 | case class TypeDef(data: ClassName) extends NodeLike[TypeDef] { 66 | type D = ClassName 67 | } 68 | case class Inheritance(from: TypeDef, to: TypeDef) extends EdgeLike[TypeDef] { 69 | type L = Option[Nothing] 70 | val label = None 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/hierarchy/ClassHierarchyBuilder.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.hierarchy 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Program 4 | 5 | class ClassHierarchyBuilder { 6 | def build(pgm: Program): ClassHierarchy = { 7 | pgm.classes.foldLeft(new ClassHierarchy) { case (ch, (_, clazz)) => 8 | clazz.interfaces.foldLeft( 9 | ch.extendsFrom(clazz.name, clazz.superClass) 10 | ) { 11 | _.implementWith(clazz.name, _) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/opt/VarSplitting.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.opt 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.GaloisIdentity 4 | import com.simplytyped.yoyak.framework.domain.MapDom 5 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 6 | import com.simplytyped.yoyak.il.CommonIL.Type 7 | import com.simplytyped.yoyak.il.CommonIL.Value.Local 8 | import com.simplytyped.yoyak.il.CommonILHelper 9 | import com.simplytyped.yoyak.il.cfg.{BasicBlock, CFG} 10 | import com.simplytyped.yoyak.il.opt.analysis.DefReachability 11 | import com.simplytyped.yoyak.il.opt.analysis.DefReachability.SetCoreStmt 12 | import scala.collection.mutable.{Set => MSet} 13 | 14 | class VarSplitting { 15 | var nameCounter = 0 16 | private def getNewLocal() = { nameCounter += 1; Local("$v" + nameCounter) } 17 | 18 | var renameMap = Map.empty[CoreStmt, Local] 19 | private def generateRenameMap( 20 | defMap: MapDom[BasicBlock, GaloisIdentity[MapDom[Local, SetCoreStmt]]] 21 | ) { 22 | val defGroup = defMap 23 | .foldLeft(Map.empty[CoreStmt, MSet[CoreStmt]]) { 24 | case (defGroupMap, (_, m)) => 25 | m.foldLeft(defGroupMap) { case (defGroupMap, (_, stmtSet)) => 26 | val existingStmt = stmtSet.find { s => 27 | defGroupMap.get(s).nonEmpty 28 | } 29 | val existingSet = if (existingStmt.nonEmpty) { 30 | defGroupMap(existingStmt.get) ++= stmtSet 31 | } else { 32 | MSet.empty[CoreStmt] ++ stmtSet 33 | } 34 | stmtSet.foldLeft(defGroupMap) { (x, y) => x + (y -> existingSet) } 35 | } 36 | } 37 | .values 38 | .toList 39 | val renameMap = defGroup.flatMap { set => 40 | val newLocal = getNewLocal() 41 | set.map { s => (s, newLocal) } 42 | }.toMap 43 | this.renameMap = renameMap 44 | } 45 | private def findNewLocal( 46 | defMap: MapDom[Local, SetCoreStmt] 47 | )(old: Local): Local = { 48 | val stmts = defMap.get(old) 49 | if (stmts.isEmpty) old 50 | else { 51 | val stmt = stmts.head 52 | val newLocalOpt = renameMap.get(stmt) 53 | if (newLocalOpt.nonEmpty) 54 | newLocalOpt.get.setType(Type.lub(old.ty, newLocalOpt.get.ty)) 55 | else { 56 | assert(stmts.size == 1) 57 | val newLocal = getNewLocal().setType(old.ty) 58 | renameMap += (stmt -> newLocal) 59 | newLocal 60 | } 61 | } 62 | } 63 | private def renameLocal( 64 | defMap: MapDom[Local, SetCoreStmt] 65 | )(stmt: CoreStmt): (MapDom[Local, SetCoreStmt], CoreStmt) = { 66 | import DefReachability.absTransfer 67 | val newStmt1 = CommonILHelper.substituteLocalUse(findNewLocal(defMap))(stmt) 68 | val nextMap = absTransfer.transfer(defMap, stmt) 69 | val newStmt2 = 70 | CommonILHelper.substituteLocalDef(findNewLocal(nextMap))(newStmt1) 71 | (nextMap, newStmt2) 72 | } 73 | def run(cfg: CFG): CFG = { 74 | val defreachAnalysis = new DefReachability 75 | val defreach = defreachAnalysis.run(cfg) 76 | generateRenameMap(defreach) 77 | cfg.nodes.foreach { bb => 78 | val prevs = cfg.getPrevs(bb) 79 | val defMap = prevs.foldLeft(DefReachability.ops.bottom) { (x, y) => 80 | DefReachability.ops.\/(x, defreach.get(y)) 81 | } 82 | val stmts = bb.data.getStmts 83 | val newStmts = stmts 84 | .foldLeft(defMap, List.empty[CoreStmt]) { case ((map, list), s) => 85 | val (newMap, newS) = renameLocal(map)(s) 86 | (newMap, newS :: list) 87 | } 88 | ._2 89 | .reverse 90 | bb.data.setStmts(newStmts) 91 | } 92 | cfg 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/il/opt/analysis/DefReachability.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.il.opt.analysis 2 | 3 | import com.simplytyped.yoyak.framework.ForwardAnalysis.FlowSensitiveForwardAnalysis 4 | import com.simplytyped.yoyak.framework.domain.Galois.{ 5 | GaloisIdentity, 6 | SetAbstraction 7 | } 8 | import com.simplytyped.yoyak.framework.domain.{Galois, LatticeOps, MapDom} 9 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable 10 | import com.simplytyped.yoyak.framework.semantics.AbstractTransferable.Context 11 | import com.simplytyped.yoyak.il.CommonIL.Statement._ 12 | import com.simplytyped.yoyak.il.CommonIL.Value.Local 13 | import com.simplytyped.yoyak.il.cfg.{BasicBlock, CFG} 14 | import com.simplytyped.yoyak.il.opt.analysis.DefReachability.SetCoreStmt 15 | 16 | class DefReachability { 17 | def run( 18 | cfg: CFG 19 | ): MapDom[BasicBlock, GaloisIdentity[MapDom[Local, SetCoreStmt]]] = { 20 | import com.simplytyped.yoyak.il.opt.analysis.DefReachability._ 21 | val analysis = new FlowSensitiveForwardAnalysis[ 22 | GaloisIdentity[MapDom[Local, SetCoreStmt]] 23 | ](cfg) 24 | val output = analysis.compute 25 | output 26 | } 27 | } 28 | 29 | object DefReachability { 30 | type SetCoreStmt = SetAbstraction[CoreStmt] 31 | 32 | implicit val ops: LatticeOps[GaloisIdentity[MapDom[Local, SetCoreStmt]]] = 33 | MapDom.ops[Local, SetCoreStmt] 34 | implicit val absTransfer 35 | : AbstractTransferable[GaloisIdentity[MapDom[Local, SetCoreStmt]]] = 36 | new AbstractTransferable[GaloisIdentity[MapDom[Local, SetCoreStmt]]] { 37 | 38 | override protected def transferInvoke( 39 | stmt: Invoke, 40 | input: MapDom[Local, SetCoreStmt] 41 | )(implicit context: Context): MapDom[Local, SetCoreStmt] = { 42 | if (stmt.ret.nonEmpty) input.update(stmt.ret.get -> Set(stmt)) 43 | else input 44 | } 45 | 46 | override protected def transferIdentity( 47 | stmt: Identity, 48 | input: MapDom[Local, SetCoreStmt] 49 | )(implicit context: Context): MapDom[Local, SetCoreStmt] = 50 | input.update(stmt.lv -> Set(stmt)) 51 | 52 | override protected def transferAssign( 53 | stmt: Assign, 54 | input: MapDom[Local, SetCoreStmt] 55 | )(implicit context: Context): MapDom[Local, SetCoreStmt] = { 56 | if (stmt.lv.isInstanceOf[Local]) 57 | input.update(stmt.lv.asInstanceOf[Local] -> Set(stmt)) 58 | else input 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/parser/cil/CommonILParser.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.cil 2 | 3 | import com.simplytyped.yoyak.parser.cil.CommonILParser._ 4 | 5 | import scala.util.parsing.combinator.JavaTokenParsers 6 | import scala.util.parsing.input.Positional 7 | 8 | class CommonILParser extends JavaTokenParsers { 9 | def clazz: Parser[Clazz] = 10 | positioned( 11 | identifier ~ "{" ~ rep(method) ~ "}" ^^ { case name ~ _ ~ list ~ _ => 12 | Clazz(name, list) 13 | } 14 | ) 15 | def method: Parser[Method] = 16 | positioned( 17 | identifier ~ "(" ~ opt(identifierWithType) ~ rep( 18 | "," ~> identifierWithType 19 | ) ~ ")" ~ block ^^ { case name ~ _ ~ firstParam ~ params ~ _ ~ body => 20 | Method(name, firstParam.toList ++ params, body) 21 | } 22 | ) 23 | def identifierWithType: Parser[(Ident, Type)] = 24 | identifier ~ ":" ~ ty ^^ { case x ~ _ ~ y => (x, y) } 25 | 26 | def block: Parser[Block] = positioned("{" ~> rep(cilstmt) <~ "}" ^^ Block) 27 | 28 | def cilstmt: Parser[CILStmt] = 29 | positioned(ifstmt | whilestmt | assignstmt | invokestmt | returnstmt) 30 | def ifstmt: Parser[If] = 31 | positioned( 32 | "if" ~ "(" ~ value ~ ")" ~ block ~ "else" ~ block ^^ { 33 | case _ ~ _ ~ v ~ _ ~ b1 ~ _ ~ b2 => If(v, b1, b2) 34 | } 35 | ) 36 | def whilestmt: Parser[While] = 37 | positioned( 38 | "while" ~ "(" ~ value ~ ")" ~ block ^^ { case _ ~ _ ~ v ~ _ ~ b => 39 | While(v, b) 40 | } 41 | ) 42 | def assignstmt: Parser[Assign] = 43 | positioned( 44 | identifier ~ "=" ~ value ~ ";" ^^ { case lv ~ _ ~ rv ~ _ => 45 | Assign(lv, rv) 46 | } 47 | ) 48 | def invokestmt: Parser[Invoke] = 49 | positioned( 50 | opt(identifier <~ "=") ~ identifier ~ "(" ~ opt(value) ~ rep( 51 | "," ~> value 52 | ) ~ ")" ~ ";" ^^ { case ret ~ callee ~ _ ~ firstOpt ~ list ~ _ ~ _ => 53 | Invoke(ret, callee, firstOpt.toList ++ list) 54 | } 55 | ) 56 | def returnstmt: Parser[Return] = 57 | positioned( 58 | "return" ~> opt(value) <~ ";" ^^ Return 59 | ) 60 | 61 | def value: Parser[Value] = 62 | positioned( 63 | cinteger ~ othervalue ^^ { case lv ~ oprvOpt => 64 | if (oprvOpt.nonEmpty) BinExp(lv, oprvOpt.get._1, oprvOpt.get._2) 65 | else lv 66 | } | 67 | cstring ~ othervalue ^^ { case lv ~ oprvOpt => 68 | if (oprvOpt.nonEmpty) BinExp(lv, oprvOpt.get._1, oprvOpt.get._2) 69 | else lv 70 | } | 71 | identifier ~ othervalue ^^ { case lv ~ oprvOpt => 72 | if (oprvOpt.nonEmpty) BinExp(lv, oprvOpt.get._1, oprvOpt.get._2) 73 | else lv 74 | } 75 | ) 76 | private def othervalue = opt(operator ~ value) 77 | def cinteger: Parser[CInteger] = 78 | positioned(wholeNumber ^^ { x => CInteger(x.toInt) }) 79 | def cstring: Parser[CString] = positioned(stringLiteral ^^ CString) 80 | def identifier: Parser[Ident] = positioned(ident ^^ Ident) 81 | 82 | def operator: Parser[Operator] = 83 | "+" ^^ { _ => Add } | 84 | "-" ^^ { _ => Sub } | 85 | "*" ^^ { _ => Mul } | 86 | "/" ^^ { _ => Div } | 87 | "==" ^^ { _ => Eq } | 88 | "!=" ^^ { _ => Ne } | 89 | "<=" ^^ { _ => Le } | 90 | ">=" ^^ { _ => Ge } | 91 | "<" ^^ { _ => Lt } | 92 | ">" ^^ { _ => Gt } 93 | 94 | def ty: Parser[Type] = 95 | "int" ^^ { _ => IntegerType } | 96 | "string" ^^ { _ => StringType } 97 | } 98 | 99 | object CommonILParser { 100 | case class Clazz(name: Ident, methods: List[Method]) extends Positional 101 | case class Method(name: Ident, params: List[(Ident, Type)], body: Block) 102 | extends Positional 103 | 104 | case class Block(stmts: List[CILStmt]) extends Positional 105 | 106 | abstract class CILStmt extends Positional 107 | case class If(cond: Value, thenBlock: Block, elseBlock: Block) extends CILStmt 108 | case class While(cond: Value, loop: Block) extends CILStmt 109 | case class Assign(lv: Ident, rv: Value) extends CILStmt 110 | case class Invoke(ret: Option[Ident], callee: Ident, args: List[Value]) 111 | extends CILStmt 112 | case class Return(v: Option[Value]) extends CILStmt 113 | 114 | abstract class Value extends Positional 115 | case class Ident(id: String) extends Value 116 | case class CInteger(v: Int) extends Value 117 | case class CString(v: String) extends Value 118 | case class BinExp(lv: Value, op: Operator, rv: Value) extends Value 119 | 120 | abstract class Operator 121 | case object Add extends Operator 122 | case object Sub extends Operator 123 | case object Mul extends Operator 124 | case object Div extends Operator 125 | case object Eq extends Operator 126 | case object Ne extends Operator 127 | case object Le extends Operator 128 | case object Ge extends Operator 129 | case object Lt extends Operator 130 | case object Gt extends Operator 131 | 132 | abstract class Type 133 | case object IntegerType extends Type 134 | case object StringType extends Type 135 | } 136 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/parser/cil/CommonILTransform.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.cil 2 | 3 | import com.simplytyped.yoyak.il.CommonIL._ 4 | import com.simplytyped.yoyak.parser.cil.{CommonILParser => CIL} 5 | 6 | class CommonILTransform { 7 | private var varIdx = 0 8 | private val thisLocal = Value.Local("$this") 9 | def getTmpVar(): Value.Local = { varIdx += 1; Value.Local(s"_tmp$varIdx") } 10 | def transformType(ty: CIL.Type): Type.ValueType = { 11 | ty match { 12 | case CIL.IntegerType => Type.IntegerType 13 | case CIL.StringType => Type.CommonTypes.String 14 | } 15 | } 16 | def transformValue(vl: CIL.Value): (Value.Loc, List[Statement.CoreStmt]) = { 17 | vl match { 18 | case CIL.Ident(id) => (Value.Local(id), List()) 19 | case CIL.CInteger(v) => 20 | val tmp = getTmpVar() 21 | (tmp, List(Statement.Assign(tmp, Value.IntegerConstant(v)))) 22 | case CIL.CString(v) => 23 | val tmp = getTmpVar() 24 | (tmp, List(Statement.Assign(tmp, Value.StringConstant(v)))) 25 | case exp: CIL.BinExp => 26 | val (rv, stmt) = transformExp(exp) 27 | val lv = getTmpVar() 28 | (lv, stmt ++ List(Statement.Assign(lv, rv))) 29 | } 30 | } 31 | def transformExp( 32 | exp: CIL.BinExp 33 | ): (Value.BinExp, List[Statement.CoreStmt]) = { 34 | val (lv, lvStmt) = transformValue(exp.lv) 35 | val (rv, rvStmt) = transformValue(exp.rv) 36 | val op = transformOp(exp.op) 37 | op match { 38 | case o: Value.BinOp.CompOp => 39 | (Value.CompBinExp(lv, o, rv), lvStmt ++ rvStmt) 40 | case o: Value.BinOp.CondOp => 41 | (Value.CondBinExp(lv, o, rv), lvStmt ++ rvStmt) 42 | } 43 | } 44 | def transformOp(op: CIL.Operator): Value.BinOp.Op = { 45 | op match { 46 | case CIL.Add => Value.BinOp.+ 47 | case CIL.Sub => Value.BinOp.- 48 | case CIL.Mul => Value.BinOp.* 49 | case CIL.Div => Value.BinOp./ 50 | case CIL.Eq => Value.BinOp.== 51 | case CIL.Ne => Value.BinOp.!= 52 | case CIL.Le => Value.BinOp.<= 53 | case CIL.Ge => Value.BinOp.>= 54 | case CIL.Lt => Value.BinOp.< 55 | case CIL.Gt => Value.BinOp.> 56 | 57 | } 58 | } 59 | def transformStmt( 60 | methodSigMap: Map[String, MethodSig] 61 | )(stmt: CIL.CILStmt): List[Statement.CoreStmt] = { 62 | stmt match { 63 | case CIL.If(cond, thenBlock, elseBlock) => 64 | val (condLoc, condStmt) = transformValue(cond) 65 | val condExp = 66 | Value.CondBinExp(condLoc, Value.BinOp.!=, Value.BooleanConstant(true)) 67 | val thenB = thenBlock.stmts.flatMap { transformStmt(methodSigMap) } 68 | val elseB = elseBlock.stmts.flatMap { transformStmt(methodSigMap) } 69 | val targetNop = Statement.Nop() 70 | val lastNop = Statement.Nop() 71 | val ifstmt = 72 | Statement.If(condExp, new Statement.Target().setStmt(targetNop)) 73 | val gotostmt = Statement.Goto(new Statement.Target().setStmt(lastNop)) 74 | condStmt ++ List(ifstmt) ++ thenB ++ List( 75 | gotostmt, 76 | targetNop 77 | ) ++ elseB ++ List(lastNop) 78 | case CIL.While(cond, loop) => 79 | val (condLoc, condStmt) = transformValue(cond) 80 | val condExp = 81 | Value.CondBinExp(condLoc, Value.BinOp.!=, Value.BooleanConstant(true)) 82 | val loopB = loop.stmts.flatMap { transformStmt(methodSigMap) } 83 | val lastNop = Statement.Nop() 84 | val firstNop = Statement.Nop() 85 | val ifstmt = 86 | Statement.If(condExp, new Statement.Target().setStmt(lastNop)) 87 | val goback = Statement.Goto(new Statement.Target().setStmt(firstNop)) 88 | List(firstNop) ++ condStmt ++ List(ifstmt) ++ loopB ++ List( 89 | goback, 90 | lastNop 91 | ) 92 | case CIL.Assign(lv, rv) => 93 | val (loc, _) = transformValue(lv) 94 | val (value, valueStmt) = transformValue(rv) 95 | valueStmt ++ List(Statement.Assign(loc, value)) 96 | case CIL.Invoke(ret, callee, args) => 97 | val retLocalOpt = 98 | ret.map { transformValue }.map { _._1.asInstanceOf[Value.Local] } 99 | val targetSig = methodSigMap(callee.id) 100 | val (argV, argStmt) = 101 | args.foldLeft(List.empty[Value.Loc], List.empty[Statement.CoreStmt]) { 102 | case ((vl, sl), a) => 103 | val (loc, stmt) = transformValue(a) 104 | (vl :+ loc, sl ++ stmt) 105 | } 106 | val invokeType = Type.DynamicInvoke(targetSig, argV, thisLocal) 107 | argStmt ++ List(Statement.Invoke(retLocalOpt, invokeType)) 108 | case CIL.Return(v) => 109 | val retLocStmt = v.map { transformValue } 110 | if (retLocStmt.nonEmpty) 111 | retLocStmt.get._2 ++ List(Statement.Return(Some(retLocStmt.get._1))) 112 | else List(Statement.Return(None)) 113 | 114 | } 115 | } 116 | def transformMethod(clsName: ClassName, methodSigMap: Map[String, MethodSig])( 117 | mtd: CIL.Method 118 | ): (MethodSig, Method) = { 119 | val methodName = mtd.name.id 120 | val (paramAssigns, methodParamTypes) = mtd.params.zipWithIndex 121 | .foldLeft(List.empty[Statement.CoreStmt], List.empty[Type.ValueType]) { 122 | case ((al, tl), ((i, t), idx)) => 123 | ( 124 | al :+ Statement.Assign(Value.Local(i.id), Value.Param(idx)), 125 | tl :+ transformType(t) 126 | ) 127 | } 128 | val methodSig = MethodSig(clsName, methodName, methodParamTypes) 129 | val body = mtd.body.stmts.flatMap { transformStmt(methodSigMap) } 130 | val assigns = Statement.Assign(thisLocal, Value.This) :: paramAssigns 131 | (methodSig, Method(methodSig, assigns ++ body)) 132 | } 133 | def extractSigs(clsName: ClassName)(mtd: CIL.Method): (String, MethodSig) = { 134 | val methodName = mtd.name.id 135 | val methodParams = mtd.params.map { x => transformType(x._2) } 136 | (methodName, MethodSig(clsName, methodName, methodParams)) 137 | } 138 | def transform(cls: CIL.Clazz): Clazz = { 139 | val className = ClassName(cls.name.id) 140 | val methodSigMap = cls.methods.map { extractSigs(className) }.toMap 141 | val methods = cls.methods.map { 142 | transformMethod(className, methodSigMap) 143 | }.toMap 144 | Clazz(className, methods, Set.empty, ClassName("java.lang.Object")) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/parser/dex/DexlibDexParser.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.dex 2 | 3 | import org.jf.dexlib2.DexFileFactory 4 | import org.jf.dexlib2.dexbacked.DexBackedDexFile 5 | 6 | object DexlibDexParser { 7 | val defaultAPIVersion = 15 8 | def loadDexFile(path: String): DexBackedDexFile = { 9 | DexFileFactory.loadDexFile(path, defaultAPIVersion) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/parser/java/AntlrJavaParser.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.java 2 | 3 | import java.io.{InputStream, FileInputStream} 4 | import com.simplytyped.yoyak.parser.JavaParser.CompilationUnitContext 5 | import com.simplytyped.yoyak.parser.{JavaParser, JavaLexer} 6 | import org.antlr.v4.runtime.{CommonTokenStream, ANTLRInputStream} 7 | 8 | class AntlrJavaParser { 9 | var rawParser: Option[JavaParser] = None 10 | def parse(filePath: String): CompilationUnitContext = 11 | parse(new FileInputStream(filePath)) 12 | def parse(is: InputStream): CompilationUnitContext = { 13 | val input = new ANTLRInputStream(is) 14 | val lexer = new JavaLexer(input) 15 | val tokens = new CommonTokenStream(lexer) 16 | rawParser = Some(new JavaParser(tokens)) 17 | val unit = rawParser.get.compilationUnit() 18 | unit 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/parser/java/AntlrJavaTransformer.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.java 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Type.RefType 4 | import com.simplytyped.yoyak.il.CommonIL._ 5 | import Statement._ 6 | import Value._ 7 | import com.simplytyped.yoyak.il.Position 8 | import com.simplytyped.yoyak.il.Position.SourceInfo 9 | import com.simplytyped.yoyak.parser.JavaParser._ 10 | import org.antlr.v4.runtime.Token 11 | import scala.collection.JavaConverters._ 12 | 13 | class AntlrJavaTransformer { 14 | private def getPositionFromToken(t: Token): Position = { 15 | new SourceInfo( 16 | t.getLine, 17 | t.getLine, 18 | t.getCharPositionInLine, 19 | t.getCharPositionInLine, 20 | t.getTokenSource.getSourceName 21 | ) 22 | } 23 | private def typeContextToType(t: TypeContext): Type.ValueType = { 24 | val dim = (t.getChildCount - 1) / 2 25 | val classOrInterfaceTy = t.classOrInterfaceType() 26 | val resultTy = 27 | if (classOrInterfaceTy != null) { 28 | val name = ClassName(classOrInterfaceTy.getText) 29 | Type.RefType(name) 30 | } else { 31 | val primTy = t.primitiveType() 32 | primTy.getText match { 33 | case "boolean" => Type.BooleanType 34 | case "char" => Type.CharType 35 | case "byte" => Type.ByteType 36 | case "short" => Type.ShortType 37 | case "int" => Type.IntegerType 38 | case "long" => Type.LongType 39 | case "float" => Type.FloatType 40 | case "double" => Type.DoubleType 41 | } 42 | } 43 | if (dim == 0) resultTy else Type.ArrayType(resultTy, dim) 44 | } 45 | private def formalParameterToIdentity( 46 | formalParam: FormalParameterContext, 47 | idx: Int 48 | ): Identity = { 49 | val name = formalParam.variableDeclaratorId().Identifier() 50 | val ty = typeContextToType(formalParam.`type`()) 51 | Statement 52 | .Identity(Local(name.getText).setType(ty), Param(idx)) 53 | .setPos(getPositionFromToken(name.getSymbol)) 54 | } 55 | def blockStatementContextToStmt( 56 | blockStmtCtx: BlockStatementContext 57 | ): List[CoreStmt] = { 58 | List.empty 59 | } 60 | def methodDefToMethod( 61 | className: ClassName, 62 | methodDef: MethodDeclarationContext 63 | ): Option[Method] = { 64 | val methodName = methodDef.Identifier().getText 65 | 66 | val params = Option(methodDef.formalParameters().formalParameterList()) 67 | .map { _.formalParameter().asScala.toList } 68 | .getOrElse(List.empty) 69 | .zipWithIndex 70 | .map { case (f, i) => formalParameterToIdentity(f, i) } 71 | val paramTy = params.map { _.lv.ty } 72 | 73 | val methodBody = 74 | methodDef.methodBody().block().blockStatement().asScala.toList.flatMap { 75 | blockStatementContextToStmt 76 | } 77 | 78 | val stmts = params ++ methodBody 79 | Some(Method(MethodSig(className, methodName, paramTy), stmts)) 80 | } 81 | def classDefToClazz(classDef: ClassDeclarationContext): Option[Clazz] = { 82 | val className = ClassName(classDef.Identifier().getSymbol.getText) 83 | val superClass = Option(classDef.`type`()) 84 | .map { typeContextToType } 85 | .map { _.asInstanceOf[RefType].className } 86 | .getOrElse(ClassName("java.lang.Object")) 87 | val interfaces = Option(classDef.typeList()) 88 | .map { 89 | _.`type`().asScala 90 | .map { typeContextToType } 91 | .map { _.asInstanceOf[RefType].className } 92 | .toSet 93 | } 94 | .getOrElse(Set.empty[ClassName]) 95 | 96 | val memberDefs = classDef 97 | .classBody() 98 | .classBodyDeclaration() 99 | .asScala 100 | .map { _.memberDeclaration() } 101 | .toList 102 | val methodDefs = memberDefs.filter { _.methodDeclaration() != null } 103 | val methods = methodDefs.flatMap { x => 104 | methodDefToMethod(className, x.methodDeclaration()) 105 | } 106 | 107 | Some( 108 | Clazz( 109 | className, 110 | methods.map { x => (x.name, x) }.toMap, 111 | interfaces, 112 | superClass 113 | ) 114 | ) 115 | } 116 | def compilationUnitToProgram(units: CompilationUnitContext): Program = { 117 | val typeDefs = units.typeDeclaration().asScala.toList 118 | val classDefs = typeDefs.filter { _.classDeclaration() != null } 119 | val classes = classDefs.flatMap { x => 120 | classDefToClazz(x.classDeclaration()) 121 | } 122 | Program(classes.map { x => (x.name, x) }.toMap) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/CfgGenPhase.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.Global 4 | import com.simplytyped.yoyak.il.cfg.CommonILToCFG 5 | 6 | class CfgGenPhase extends Phase { 7 | override def run(g: Global): Global = { 8 | assert(g.pgm.nonEmpty) 9 | val toCFG = new CommonILToCFG 10 | val newClasses = g.pgm.get.classes.map { case (cn, cl) => 11 | val newMethods = cl.methods.map { case (sig, mtd) => 12 | val cfg = toCFG.transform(mtd) 13 | mtd.cfg = Some(cfg) 14 | (sig, mtd) 15 | } 16 | (cn, cl.copy(methods = newMethods)) 17 | } 18 | val newPgm = g.pgm.get.copy(classes = newClasses) 19 | g.copy(pgm = Some(newPgm)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/ClassHierarchyGenPhase.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.Global 4 | import com.simplytyped.yoyak.il.hierarchy.ClassHierarchyBuilder 5 | 6 | class ClassHierarchyGenPhase extends Phase { 7 | override def run(g: Global): Global = { 8 | assert(g.pgm.nonEmpty) 9 | val chBuilder = new ClassHierarchyBuilder 10 | val ch = chBuilder.build(g.pgm.get) 11 | g.copy(classHierarchy = Some(ch)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/DexParserPhase.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.{Global, Options} 4 | import com.simplytyped.yoyak.parser.dex.{DexlibDexParser, DexlibDexTransformer} 5 | 6 | class DexParserPhase extends Phase { 7 | override def run(g: Global): Global = { 8 | assert(Options.g.target_apk.nonEmpty) 9 | val target = Options.g.target_apk.get 10 | val dexFile = DexlibDexParser.loadDexFile(target) 11 | val pgm = (new DexlibDexTransformer).translate(dexFile) 12 | g.copy(pgm = Some(pgm)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/Phase.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.Global 4 | 5 | trait Phase { 6 | private var nextPhase: Option[Phase] = None 7 | 8 | def dependsOn(prevPhase: Phase) { prevPhase.nextPhase = Some(this) } 9 | def hasNext: Boolean = nextPhase.nonEmpty 10 | def next = nextPhase.get 11 | 12 | def run(g: Global): Global 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/PhaseDriver.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.Global 4 | 5 | class PhaseDriver(firstPhase: Phase) { 6 | def run(g: Global): Global = { 7 | var currentPhase = firstPhase 8 | var currentGlobal = currentPhase.run(g) 9 | while (currentPhase.hasNext) { 10 | currentPhase = currentPhase.next 11 | currentGlobal = currentPhase.run(currentGlobal) 12 | } 13 | currentGlobal 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/StringAnalysisPhase.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.Global 4 | import com.simplytyped.yoyak.analysis.StringAnalysis 5 | 6 | class StringAnalysisPhase extends Phase { 7 | override def run(g: Global): Global = { 8 | assert(g.pgm.nonEmpty) 9 | for ( 10 | (_, cls) <- g.pgm.get.classes; (_, mtd) <- cls.methods; cfg <- mtd.cfg 11 | ) { 12 | val analysis = new StringAnalysis(cfg) 13 | println(analysis.run()) 14 | } 15 | g 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/phases/VarSplittingPhase.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.phases 2 | 3 | import com.simplytyped.yoyak.Global 4 | import com.simplytyped.yoyak.il.opt.VarSplitting 5 | 6 | class VarSplittingPhase extends Phase { 7 | override def run(g: Global): Global = { 8 | assert(g.pgm.nonEmpty) 9 | for ( 10 | (_, cls) <- g.pgm.get.classes; (_, mtd) <- cls.methods; cfg <- mtd.cfg 11 | ) { 12 | val varsplitting = new VarSplitting 13 | varsplitting.run(cfg) 14 | } 15 | g 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/algo/BCP.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.algo 2 | 3 | import com.simplytyped.yoyak.solver.domain._ 4 | 5 | /** Created with IntelliJ IDEA. 6 | * User: ihji 7 | * Date: 5/13/12 8 | * Time: 7:43 PM 9 | * To change this template use File | Settings | File Templates. 10 | */ 11 | 12 | class BCP(cnf: CNF) { 13 | def unit(p: PAssignDom, c: Clause): PAssignDom = { 14 | p match { 15 | case x: PAssign => 16 | val undet = c.vars.filterNot { x.get(_) == F } 17 | if (undet.size == 0) PAssignTop(x) 18 | else if (undet.size == 1 && x.get(undet.head) == Bot) 19 | x.update(undet.head, T) 20 | else x 21 | case x: PAssignTop => x 22 | } 23 | } 24 | def run(phi: PAssignDom): PAssignDom = { 25 | var oldPhi: PAssignDom = PAssign.empty 26 | var newPhi: PAssignDom = phi 27 | do { 28 | oldPhi = newPhi 29 | newPhi = cnf.clauses.foldLeft(newPhi) { unit(_, _) } 30 | } while (!(newPhi <<= oldPhi).get) 31 | newPhi 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/algo/CDCL.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.algo 2 | 3 | import com.simplytyped.yoyak.solver.domain._ 4 | import annotation.tailrec 5 | 6 | /** Created with IntelliJ IDEA. 7 | * User: ihji 8 | * Date: 5/14/12 9 | * Time: 4:13 PM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | 13 | class CDCL(cnf: CNF) { 14 | var ccnf: CNF = cnf 15 | def search(p: PAssignTop): Clause = { 16 | Clause(List.empty) // ??? 17 | } 18 | def prove(cases: List[PAssign]): (List[PAssign], List[PAssignTop]) = { 19 | val bcp = new BCP(ccnf) 20 | val qcp = new QCP(ccnf) 21 | val (pt, pa) = cases.map { bcp.run }.partition { _.isTop } 22 | ( 23 | qcp.split(pa.asInstanceOf[List[PAssign]]), 24 | pt.asInstanceOf[List[PAssignTop]] 25 | ) 26 | } 27 | @tailrec 28 | final def run(cases: List[PAssign]): Option[PAssign] = { 29 | val (pa, pt) = prove(cases) 30 | if (pa.isEmpty) None 31 | else { 32 | val satCases = pa.filter { _.size == cnf.nbVars } 33 | if (satCases.size > 0) Some(satCases.head) 34 | else { 35 | ccnf = ccnf.addClauses(pt.map { search }) 36 | run(pa) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/algo/QCP.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.algo 2 | 3 | import com.simplytyped.yoyak.solver.domain._ 4 | import annotation.tailrec 5 | 6 | /** Created with IntelliJ IDEA. 7 | * User: ihji 8 | * Date: 5/14/12 9 | * Time: 2:12 AM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | 13 | class QCP(cnf: CNF) { 14 | val bcp = new BCP(cnf) 15 | def pick(p: PAssign): Int = { 16 | ((1 to cnf.nbVars).toSet -- p.map.t.keySet).head 17 | } 18 | def split(l: List[PAssign]): List[PAssign] = { 19 | l.foldLeft(List.empty[PAssign], true) { case ((newl, flag), a) => 20 | if (flag && a.size < cnf.nbVars) { 21 | val v = pick(a) 22 | (a.update(v, T) :: a.update(v, F) :: newl, false) 23 | } else (a :: newl, flag) 24 | }._1 25 | } 26 | @tailrec 27 | final def run(cases: List[PAssign]): Option[PAssign] = { 28 | val cases$ = 29 | cases.map { bcp.run }.filterNot { _.isTop }.asInstanceOf[List[PAssign]] 30 | if (cases$.isEmpty) None 31 | else { 32 | val satCases = cases$.filter { _.size == cnf.nbVars } 33 | if (satCases.size > 0) Some(satCases.head) 34 | else run(split(cases$)) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/domain/AbsDomLike.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | trait AbsDomLike[D <: AbsDomLike[D]] { 4 | def isTop: Boolean 5 | def <<=(other: D): Option[Boolean] 6 | def ++(other: D): D 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/domain/CNF.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | /** Created with IntelliJ IDEA. 4 | * User: ihji 5 | * Date: 5/13/12 6 | * Time: 5:41 PM 7 | * To change this template use File | Settings | File Templates. 8 | */ 9 | 10 | case class CNF(nbVars: Int, nbClauses: Int, clauses: List[Clause]) { 11 | def addClauses(c: List[Clause]): CNF = 12 | copy(nbClauses = nbClauses + c.length, clauses = c ++ clauses) 13 | } 14 | 15 | case class Clause(vars: List[Int]) 16 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/domain/CNFConversions.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | import scala.language.implicitConversions 4 | 5 | /** Created with IntelliJ IDEA. 6 | * User: ihji 7 | * Date: 5/13/12 8 | * Time: 8:59 PM 9 | * To change this template use File | Settings | File Templates. 10 | */ 11 | 12 | object CNFConversions { 13 | implicit def dimacs2cnf(dimacs: String): CNF = { 14 | val parser = new DIMACSParser {} 15 | val result = parser.parseAll(parser.cnf, dimacs) 16 | if (result.successful) result.get.get 17 | else throw new Exception(result.toString) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/domain/DIMACSParser.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | import util.parsing.combinator.RegexParsers 4 | import com.simplytyped.yoyak.util.Log 5 | 6 | /** Created with IntelliJ IDEA. 7 | * User: ihji 8 | * Date: 5/13/12 9 | * Time: 5:32 PM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | 13 | trait DIMACSParser extends RegexParsers { 14 | def cnf: Parser[Option[CNF]] = 15 | rep(comment) ~> definition ~ rep1(clause) ^^ { case (nbv, nbc) ~ cs => 16 | if (cs.length != nbc) { 17 | Log.error("the number of clauses is wrong"); None 18 | } else if (cs.exists { _.exists { x => x > nbv || x < -nbv } }) { 19 | Log.error("var should be in [-%s,%s]".format(nbv, nbv)); None 20 | } else Some(CNF(nbv, nbc, cs.map { Clause(_) })) 21 | } 22 | 23 | def comment: Parser[String] = "c" ~> ".*".r ^^ { identity } 24 | def definition: Parser[(Int, Int)] = 25 | "p" ~> "cnf" ~> "[1-9][0-9]*".r ~ "[1-9][0-9]*".r ^^ { case x ~ y => 26 | (x.toInt, y.toInt) 27 | } 28 | def clause: Parser[List[Int]] = 29 | rep1("-?[1-9][0-9]*".r) <~ "0" ^^ { _.map { _.toInt } } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/domain/MapDom.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | case class MapDom[D1, D2 <: AbsDomLike[D2]](t: Map[D1, D2]) 4 | extends AbsDomLike[MapDom[D1, D2]] { 5 | 6 | override def toString = "(%s):%s".format(t.size, t.mkString(", ")) 7 | 8 | def isTop = false 9 | 10 | def get(key: D1): Option[D2] = t.get(key) 11 | 12 | def getOrElse(key: D1, v: D2): D2 = t.getOrElse(key, v) 13 | 14 | def update(key: D1, elem: D2): MapDom[D1, D2] = copy(t = t + (key -> elem)) 15 | 16 | def weakUpdate(key: D1, elem: D2): MapDom[D1, D2] = 17 | get(key) match { 18 | case Some(elem_) => update(key, elem ++ elem_) 19 | case None => update(key, elem) 20 | } 21 | 22 | def <<=(other: MapDom[D1, D2]): Option[Boolean] = { 23 | if (t.size > other.t.size) { 24 | if ( 25 | other.t.foldLeft(true) { case (b, (k, v)) => 26 | t.get(k).flatMap { v <<= _ }.getOrElse(false) && b 27 | } 28 | ) Some(false) 29 | else None 30 | } else if (t.size < other.t.size) { 31 | if ( 32 | t.foldLeft(true) { case (b, (k, v)) => 33 | other.t.get(k).flatMap { v <<= _ }.getOrElse(false) && b 34 | } 35 | ) Some(true) 36 | else None 37 | } else { 38 | val (lt, rt) = t.foldLeft(true, true) { case ((l, r), (k, v)) => 39 | val (l$, r$) = other.t 40 | .get(k) 41 | .map { x => 42 | ((v <<= x).getOrElse(false), (x <<= v).getOrElse(false)) 43 | } 44 | .getOrElse(false, false) 45 | (l && l$, r && r$) 46 | } 47 | if (lt && rt) Some(true) 48 | else if (lt && !rt) Some(true) 49 | else if (!lt && rt) Some(false) 50 | else None 51 | } 52 | } 53 | 54 | def ++(other: MapDom[D1, D2]) = 55 | t.foldLeft(other) { (m, pair) => 56 | m.weakUpdate(pair._1, pair._2) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/solver/domain/PAssignDom.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | import util.parsing.combinator.RegexParsers 4 | import scala.language.implicitConversions 5 | 6 | /** Created with IntelliJ IDEA. 7 | * User: ihji 8 | * Date: 5/13/12 9 | * Time: 6:34 PM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | 13 | sealed abstract class Bool extends AbsDomLike[Bool] { 14 | def neg: Bool 15 | } 16 | case object Top extends Bool { 17 | def isTop = true 18 | 19 | def neg = Top 20 | 21 | def <<=(other: Bool) = 22 | if (other == Top) Some(true) else Some(false) 23 | 24 | def ++(other: Bool) = Top 25 | } 26 | sealed abstract class ConcreteBool extends Bool 27 | case object F extends ConcreteBool { 28 | def isTop = false 29 | 30 | def neg = T 31 | 32 | def <<=(other: Bool) = 33 | other match { 34 | case Top => Some(true) 35 | case T => None 36 | case F => Some(true) 37 | case Bot => Some(false) 38 | } 39 | 40 | def ++(other: Bool) = 41 | other match { 42 | case Top => Top 43 | case T => Top 44 | case F => F 45 | case Bot => F 46 | } 47 | } 48 | case object T extends ConcreteBool { 49 | def isTop = false 50 | 51 | def neg = F 52 | 53 | def <<=(other: Bool) = 54 | other match { 55 | case Top => Some(true) 56 | case T => Some(true) 57 | case F => None 58 | case Bot => Some(false) 59 | } 60 | 61 | def ++(other: Bool) = 62 | other match { 63 | case Top => Top 64 | case T => T 65 | case F => Top 66 | case Bot => T 67 | } 68 | } 69 | case object Bot extends Bool { 70 | def isTop = false 71 | 72 | def neg = Bot 73 | 74 | def <<=(other: Bool) = Some(true) 75 | 76 | def ++(other: Bool) = 77 | if (other == Bot) Bot else other 78 | } 79 | 80 | sealed abstract class PAssignDom extends AbsDomLike[PAssignDom] 81 | 82 | case class PAssign(map: MapDom[Int, Bool]) extends PAssignDom { 83 | def isTop = false 84 | 85 | def get(v: Int): Bool = 86 | if (v < 0) map.getOrElse(-v, Bot).neg else map.getOrElse(v, Bot) 87 | 88 | def update(v: Int, b: ConcreteBool) = { 89 | val newMap = if (v < 0) map.update(-v, b.neg) else map.update(v, b) 90 | copy(map = newMap) 91 | } 92 | 93 | def size = map.t.size 94 | 95 | def <<=(other: PAssignDom) = 96 | other match { 97 | case _: PAssignTop => Some(true) 98 | case PAssign(otherMap) => map.<<=(otherMap) 99 | } 100 | 101 | def ++(other: PAssignDom) = 102 | other match { 103 | case _: PAssignTop => other 104 | case PAssign(otherMap) => 105 | val newMap = map.++(otherMap) 106 | if (newMap.t.exists { !_._2.isInstanceOf[ConcreteBool] }) 107 | PAssignTop(copy(map = newMap)) 108 | else copy(map = newMap) 109 | } 110 | } 111 | 112 | object PAssign { 113 | val empty = PAssign(MapDom(Map.empty)) 114 | class Parser extends RegexParsers { 115 | def bool: Parser[Bool] = 116 | "0" ^^ { _ => F } | "1" ^^ { _ => T } 117 | def assign: Parser[PAssign] = 118 | rep1(bool) ^^ { l => PAssign(MapDom((1 to l.length).zip(l).toMap)) } 119 | } 120 | implicit def str2PAssign(str: String): PAssign = { 121 | val parser = new Parser 122 | val result = parser.parseAll(parser.assign, str) 123 | if (result.successful) result.get 124 | else throw new Exception(result.toString) 125 | } 126 | } 127 | 128 | case class PAssignTop(conf: PAssign) extends PAssignDom { 129 | def isTop = true 130 | def <<=(other: PAssignDom) = 131 | if (other.isInstanceOf[PAssignTop]) Some(true) else Some(false) 132 | def ++(other: PAssignDom) = this 133 | } 134 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/util/Log.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.util 2 | 3 | import org.apache.logging.log4j.LogManager 4 | 5 | object Log { 6 | val log = LogManager.getLogger("yoyak") 7 | def error(msg: => String) { 8 | if (log.isErrorEnabled) log error msg 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/com/simplytyped/yoyak/util/Option.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.util 2 | 3 | object Option {} 4 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/analysis/IntervalAnalysisTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.analysis 2 | 3 | import com.simplytyped.yoyak.il.CommonIL.Program 4 | import com.simplytyped.yoyak.il.PrettyPrinter 5 | import com.simplytyped.yoyak.il.cfg.CommonILToCFG 6 | import com.simplytyped.yoyak.parser.cil.{CommonILParser, CommonILTransform} 7 | import org.scalatest.funsuite.AnyFunSuite 8 | import org.scalatest.matchers.should.Matchers 9 | 10 | class IntervalAnalysisTest extends AnyFunSuite with Matchers { 11 | test("simple test") { 12 | val parser = new CommonILParser 13 | val result = parser.parseAll(parser.clazz, IntervalAnalysisTest.inputPgm) 14 | if (!result.successful) println(result) 15 | val clazz = new CommonILTransform().transform(result.get) 16 | val pgm = Program(Map(clazz.name -> clazz)) 17 | val mtd = pgm.findByMethodName("bar").head 18 | val cfg = new CommonILToCFG().transform(mtd) 19 | //println(new PrettyPrinter().toDot(cfg)) 20 | println(new PrettyPrinter().toString(mtd)) 21 | 22 | val result2 = new IntervalAnalysis(cfg).run() 23 | 24 | println(result2) 25 | 26 | pgm.findByMethodName("bar").nonEmpty should be(true) 27 | } 28 | } 29 | 30 | object IntervalAnalysisTest { 31 | val inputPgm = 32 | """ 33 | | testclass { 34 | | foo(x: int, y: string) { 35 | | a = x; 36 | | b = y; 37 | | bar(b); 38 | | return b; 39 | | } 40 | | bar(a: string) { 41 | | x = 0; 42 | | y = 1; 43 | | while (x < 100) { 44 | | x = x + 1; 45 | | } 46 | | if(y > 0) { 47 | | y = 10; 48 | | return 1; 49 | | } else { 50 | | y = -10; 51 | | return 0; 52 | | } 53 | | } 54 | | } 55 | """.stripMargin 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/framework/domain/MapDomTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.SetAbstraction 4 | import com.simplytyped.yoyak.framework.domain.MapDomTest.SetInt 5 | import org.scalatest.funsuite.AnyFunSuite 6 | import org.scalatest.matchers.should.Matchers 7 | 8 | class MapDomTest extends AnyFunSuite with Matchers { 9 | test("MapDom ordering (<=) test: unrelated") { 10 | val map1 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3)) 11 | val map2 = MapDom.empty[Int, SetInt].update(2 -> Set(1, 2, 3)) 12 | val ordering = MapDom.ops[Int, SetInt] 13 | 14 | ordering.partialCompare(map1, map2).isNaN should be(true) 15 | } 16 | test("MapDom ordering (<=) test: lhs is bigger") { 17 | val map1 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3, 4)) 18 | val map2 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3)) 19 | val ordering = MapDom.ops[Int, SetInt] 20 | 21 | ordering.partialCompare(map1, map2) should be(1.0) 22 | } 23 | test("MapDom ordering (<=) test: lhs is bigger 2") { 24 | val map1 = MapDom 25 | .empty[Int, SetInt] 26 | .update(1 -> Set(1, 2, 3)) 27 | .update(2 -> Set(1, 3, 4)) 28 | val map2 = 29 | MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3)).update(2 -> Set(1)) 30 | val ordering = MapDom.ops[Int, SetInt] 31 | 32 | ordering.partialCompare(map1, map2) should be(1.0) 33 | } 34 | test("MapDom ordering (<=) test: rhs is bigger") { 35 | val map1 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3)) 36 | val map2 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3, 4)) 37 | val ordering = MapDom.ops[Int, SetInt] 38 | 39 | ordering.partialCompare(map1, map2) should be(-1.0) 40 | } 41 | test("MapDom ordering (<=) test: lhs has more elements") { 42 | val map1 = 43 | MapDom.empty[Int, SetInt].update(1 -> Set(1, 2)).update(2 -> Set(2, 3, 4)) 44 | val map2 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3)) 45 | val ordering = MapDom.ops[Int, SetInt] 46 | 47 | ordering.partialCompare(map1, map2).isNaN should be(true) 48 | } 49 | test("MapDom ordering (<=) test: rhs has more elements") { 50 | val map1 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3)) 51 | val map2 = MapDom 52 | .empty[Int, SetInt] 53 | .update(1 -> Set(1, 2, 3)) 54 | .update(2 -> Set(2, 3, 4)) 55 | val ordering = MapDom.ops[Int, SetInt] 56 | 57 | ordering.partialCompare(map1, map2) should be(-1.0) 58 | } 59 | test( 60 | "MapDom ordering (<=) test: rhs has more elements, but lhs has a bigger element" 61 | ) { 62 | val map1 = MapDom.empty[Int, SetInt].update(1 -> Set(1, 2, 3, 4)) 63 | val map2 = MapDom 64 | .empty[Int, SetInt] 65 | .update(1 -> Set(1, 2, 3)) 66 | .update(2 -> Set(2, 3, 4)) 67 | val ordering = MapDom.ops[Int, SetInt] 68 | 69 | ordering.partialCompare(map1, map2).isNaN should be(true) 70 | } 71 | } 72 | 73 | object MapDomTest { 74 | type SetInt = SetAbstraction[Int] 75 | } 76 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/framework/domain/arith/IntervalTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.arith 2 | 3 | import com.simplytyped.yoyak.framework.domain.arith.Interval._ 4 | import com.simplytyped.yoyak.framework.domain.arith.IntervalInt.arithOps 5 | import org.scalatest.funsuite.AnyFunSuite 6 | import org.scalatest.matchers.should.Matchers 7 | 8 | class IntervalTest extends AnyFunSuite with Matchers { 9 | test("add operation 1") { 10 | val interv2 = Interv.of(2) 11 | val interv3 = Interv.of(3) 12 | val interv5 = Interv.of(5) 13 | arithOps.+(interv2, interv3) should be(interv5) 14 | } 15 | test("add operation 2") { 16 | val interv1 = Interv.in(IInt(-2), IInt(3)) 17 | val interv2 = Interv.of(3) 18 | val interv3 = Interv.in(IInt(1), IInt(6)) 19 | arithOps.+(interv1, interv2) should be(interv3) 20 | } 21 | test("add operation 3") { 22 | val interv1 = Interv.in(IInfMinus, IInt(3)) 23 | val interv2 = Interv.of(100) 24 | val interv3 = Interv.in(IInfMinus, IInt(103)) 25 | arithOps.+(interv1, interv2) should be(interv3) 26 | } 27 | test("add operation 4") { 28 | val interv1 = Interv.in(IInfMinus, IInf) 29 | val interv2 = Interv.of(100) 30 | val interv3 = IntervTop 31 | arithOps.+(interv1, interv2) should be(interv3) 32 | } 33 | test("add operation 5") { 34 | val interv1 = Interv.in(IInfMinus, IInt(32)) 35 | val interv2 = Interv.in(IInt(100), IInf) 36 | val interv3 = IntervTop 37 | arithOps.+(interv1, interv2) should be(interv3) 38 | } 39 | test("sub operation 1") { 40 | val interv2 = Interv.of(2) 41 | val interv3 = Interv.of(3) 42 | val intervM1 = Interv.of(-1) 43 | arithOps.-(interv2, interv3) should be(intervM1) 44 | } 45 | test("sub operation 2") { 46 | val interv1 = Interv.in(IInt(-2), IInt(3)) 47 | val interv2 = Interv.of(3) 48 | val interv3 = Interv.in(IInt(-5), IInt(0)) 49 | arithOps.-(interv1, interv2) should be(interv3) 50 | } 51 | test("sub operation 3") { 52 | val interv1 = Interv.in(IInfMinus, IInt(3)) 53 | val interv2 = Interv.of(100) 54 | val interv3 = Interv.in(IInfMinus, IInt(-97)) 55 | arithOps.-(interv1, interv2) should be(interv3) 56 | } 57 | test("sub operation 4") { 58 | val interv1 = Interv.in(IInfMinus, IInf) 59 | val interv2 = Interv.of(100) 60 | val interv3 = IntervTop 61 | arithOps.-(interv1, interv2) should be(interv3) 62 | } 63 | test("sub operation 5") { 64 | val interv1 = Interv.in(IInfMinus, IInt(32)) 65 | val interv2 = Interv.in(IInt(100), IInf) 66 | val interv3 = Interv.in(IInfMinus, IInt(-68)) 67 | arithOps.-(interv1, interv2) should be(interv3) 68 | } 69 | test("mult operation 1") { 70 | val interv2 = Interv.of(2) 71 | val interv3 = Interv.of(3) 72 | val interv6 = Interv.of(6) 73 | arithOps.*(interv2, interv3) should be(interv6) 74 | } 75 | test("mult operation 2") { 76 | val interv1 = Interv.in(IInt(-2), IInt(3)) 77 | val interv2 = Interv.of(3) 78 | val interv3 = Interv.in(IInt(-6), IInt(9)) 79 | arithOps.*(interv1, interv2) should be(interv3) 80 | } 81 | test("mult operation 3") { 82 | val interv1 = Interv.in(IInfMinus, IInt(32)) 83 | val interv2 = Interv.in(IInt(100), IInf) 84 | val interv3 = IntervTop 85 | arithOps.*(interv1, interv2) should be(interv3) 86 | } 87 | test("div operation 1") { 88 | val interv1 = Interv.of(15) 89 | val interv2 = Interv.of(3) 90 | val interv3 = Interv.of(5) 91 | arithOps./(interv1, interv2) should be(interv3) 92 | } 93 | test("div operation 2") { 94 | val interv1 = Interv.in(IInt(-99), IInt(3)) 95 | val interv2 = Interv.of(3) 96 | val interv3 = Interv.in(IInt(-33), IInt(1)) 97 | arithOps./(interv1, interv2) should be(interv3) 98 | } 99 | test("div operation 3") { 100 | val interv1 = Interv.in(IInfMinus, IInt(32)) 101 | val interv2 = Interv.in(IInt(100), IInf) 102 | val interv3 = Interv.in(IInfMinus, IInt(0)) 103 | arithOps./(interv1, interv2) should be(interv3) 104 | } 105 | test("div operation 4") { 106 | val interv1 = Interv.of(15) 107 | val interv2 = Interv.of(0) 108 | val interv3 = IntervBottom 109 | arithOps./(interv1, interv2) should be(interv3) 110 | } 111 | test("div operation 5") { 112 | val interv1 = Interv.in(IInfMinus, IInt(0)) 113 | val interv2 = Interv.of(0) 114 | val interv3 = IntervBottom 115 | arithOps./(interv1, interv2) should be(interv3) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/framework/domain/mem/MemDomTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.framework.domain.mem 2 | 3 | import com.simplytyped.yoyak.framework.domain.Galois.SetAbstraction 4 | import com.simplytyped.yoyak.framework.domain.MapDomTest.SetInt 5 | import com.simplytyped.yoyak.framework.domain.mem.MemDomTest.SetString 6 | import com.simplytyped.yoyak.framework.domain.{ 7 | ArithmeticOps, 8 | Galois, 9 | LatticeWithTopOps 10 | } 11 | import com.simplytyped.yoyak.framework.domain.mem.MemElems._ 12 | import com.simplytyped.yoyak.il.CommonIL.ClassName 13 | import com.simplytyped.yoyak.il.CommonIL.Statement.Nop 14 | import com.simplytyped.yoyak.il.CommonIL.Value._ 15 | import org.scalatest.funsuite.AnyFunSuite 16 | import org.scalatest.matchers.should.Matchers 17 | 18 | class MemDomTest extends AnyFunSuite with Matchers { 19 | import com.simplytyped.yoyak.framework.domain.mem.MemDomTest.arithOps 20 | test("add and retrieve things") { 21 | val mem = MemDom.empty[SetInt, SetString] 22 | val mem2 = mem.update(Local("x") -> AbsBox[SetString](Set("x"))) 23 | mem2.get(Local("x")) should be(AbsBox[SetString](Set("x"))) 24 | } 25 | test("add and retrieve things in instance field") { 26 | val mem = MemDom.empty[SetInt, SetString] 27 | val (newRef, mem2) = mem.alloc(Nop()) 28 | val mem3 = mem2.update(Local("x") -> newRef) 29 | val mem4 = mem3.update( 30 | InstanceFieldRef(Local("x"), "f") -> AbsArith[SetInt](Set(10)) 31 | ) 32 | mem4.get(InstanceFieldRef(Local("x"), "f")) should be( 33 | AbsArith[SetInt](Set(10)) 34 | ) 35 | mem4.get(InstanceFieldRef(Local("x"), "g")) should be(AbsBottom) 36 | } 37 | test("add and retrieve things in static field") { 38 | val mem = MemDom.empty[SetInt, SetString] 39 | val mem2 = mem.update( 40 | StaticFieldRef(ClassName("yoyak.Test"), "f") -> AbsArith[SetInt](Set(10)) 41 | ) 42 | mem2.get(StaticFieldRef(ClassName("yoyak.Test"), "f")) should be( 43 | AbsArith[SetInt](Set(10)) 44 | ) 45 | mem2.get(StaticFieldRef(ClassName("yoyak.Test"), "g")) should be(AbsBottom) 46 | } 47 | test("add and retrieve things in array") { 48 | val mem = MemDom.empty[SetInt, SetString] 49 | val (newref, mem2) = mem.alloc(Nop()) 50 | val mem3 = mem2.update(Local("x") -> newref) 51 | val mem4 = mem3.update( 52 | ArrayRef(Local("x"), IntegerConstant(1)) -> AbsArith[SetInt](Set(10)) 53 | ) 54 | mem4.get(ArrayRef(Local("x"), IntegerConstant(1))) should be( 55 | AbsArith[SetInt](Set(10)) 56 | ) 57 | mem4.get(ArrayRef(Local("x"), IntegerConstant(2))) should be( 58 | AbsArith[SetInt](Set(10)) 59 | ) 60 | } 61 | test("add and retrieve things in array with join") { 62 | val mem = MemDom.empty[SetInt, SetString] 63 | val (newref, mem2) = mem.alloc(Nop()) 64 | val mem3 = mem2.update(Local("x") -> newref) 65 | val mem4 = mem3.update( 66 | ArrayRef(Local("x"), IntegerConstant(1)) -> AbsArith[SetInt](Set(10)) 67 | ) 68 | val mem5 = mem4.update( 69 | ArrayRef(Local("x"), IntegerConstant(2)) -> AbsArith[SetInt](Set(20)) 70 | ) 71 | mem5.get(ArrayRef(Local("x"), IntegerConstant(1))) should be( 72 | AbsArith[SetInt](Set(10, 20)) 73 | ) 74 | mem5.get(ArrayRef(Local("x"), IntegerConstant(2))) should be( 75 | AbsArith[SetInt](Set(10, 20)) 76 | ) 77 | } 78 | } 79 | 80 | object MemDomTest { 81 | type SetString = SetAbstraction[String] 82 | 83 | implicit val arithOps: ArithmeticOps[SetInt] = new ArithmeticOps[SetInt] { 84 | override def +(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 85 | 86 | override def /(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 87 | 88 | override def -(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 89 | 90 | override def *(lhs: Set[Int], rhs: Set[Int]): Set[Int] = ??? 91 | 92 | override def lift(const: Constant): Set[Int] = ??? 93 | 94 | override def unlift(abs: Set[Int]): Option[Set[Int]] = ??? 95 | 96 | override def isTop(v: Set[Int]): Boolean = false 97 | 98 | override def bottom: Set[Int] = Set.empty[Int] 99 | 100 | override def \/(lhs: Set[Int], rhs: Set[Int]): Set[Int] = lhs ++ rhs 101 | 102 | override def partialCompare(lhs: Set[Int], rhs: Set[Int]): Double = 103 | if (lhs == rhs) 0.0 104 | else if (lhs subsetOf rhs) -1.0 105 | else if (rhs subsetOf lhs) 1.0 106 | else Double.NaN 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/graph/GraphGenerator.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | import org.scalacheck.Gen 4 | import org.scalacheck.Arbitrary.arbitrary 5 | 6 | /** automatic test graph generator 7 | */ 8 | object GraphGenerator { 9 | case class IntNode(data: Int) extends NodeLike[IntNode] { type D = Int } 10 | case class IntEdge(from: IntNode, to: IntNode) extends EdgeLike[IntNode] { 11 | type L = Option[Nothing]; val label = None 12 | } 13 | case class IntegerImmutableGraph( 14 | nodes: Set[IntNode], 15 | edges: Set[IntEdge], 16 | nexts: Map[IntNode, Set[IntEdge]], 17 | prevs: Map[IntNode, Set[IntEdge]] 18 | ) extends ImmutableGraphLike[IntNode, IntEdge, IntegerImmutableGraph] { 19 | 20 | def newEdge(from: IntNode, to: IntNode): IntEdge = IntEdge(from, to) 21 | 22 | def builder( 23 | nodes: Set[IntNode], 24 | edges: Set[IntEdge], 25 | nexts: Map[IntNode, Set[IntEdge]], 26 | prevs: Map[IntNode, Set[IntEdge]] 27 | ): IntegerImmutableGraph = { 28 | IntegerImmutableGraph(nodes, edges, nexts, prevs) 29 | } 30 | } 31 | object IntegerImmutableGraph { 32 | val empty = 33 | IntegerImmutableGraph(Set.empty, Set.empty, Map.empty, Map.empty) 34 | implicit def str2graph(s: String): IntegerImmutableGraph = { 35 | val parser = new IntegerGraphParser 36 | parser.parseAll(parser.graph, s).get 37 | } 38 | } 39 | 40 | val singleGraphGen: Gen[IntegerImmutableGraph] = for { 41 | from <- Gen.choose(1, 100) 42 | to <- Gen.choose(1, 100) 43 | } yield { 44 | IntegerImmutableGraph.empty.addEdge(IntNode(from), IntNode(to)) 45 | } 46 | 47 | def joinGraphGen(count: Int): Gen[IntegerImmutableGraph] = 48 | for { 49 | _ <- arbitrary[Int] 50 | first <- graphGenAux(count) 51 | second <- graphGenAux(count) 52 | firstIndex <- Gen.choose(0, first.nodes.size - 1) 53 | secondIndex <- Gen.choose(0, second.nodes.size - 1) 54 | } yield { 55 | (first ++ second).addEdge( 56 | first.nodes.toList(firstIndex), 57 | second.nodes.toList(secondIndex) 58 | ) 59 | } 60 | def graphGenAux(count: Int = 0): Gen[IntegerImmutableGraph] = 61 | if (count < 5) joinGraphGen(count + 1) 62 | else if (count > 50) singleGraphGen 63 | else Gen.oneOf(singleGraphGen, joinGraphGen(count + 1)) 64 | def graphGen = graphGenAux() 65 | } 66 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/graph/ImmutableGraphLikeSpecTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import org.scalatestplus.scalacheck.Checkers 6 | import org.scalacheck.Prop._ 7 | import com.simplytyped.yoyak.graph.GraphGenerator.{ 8 | IntEdge, 9 | IntNode, 10 | IntegerImmutableGraph 11 | } 12 | 13 | /** Graph build test 14 | */ 15 | class ImmutableGraphLikeSpecTest extends AnyFunSuite with Matchers with Checkers { 16 | test("add one edge") { 17 | check { 18 | forAll(GraphGenerator.graphGen) { (g: IntegerImmutableGraph) => 19 | val edge = IntEdge(IntNode(101), IntNode(102)) 20 | g.nodes.size == (g.addEdge(edge).nodes.size - 2) 21 | } 22 | } 23 | check { 24 | forAll(GraphGenerator.graphGen) { (g: IntegerImmutableGraph) => 25 | val edge = IntEdge(IntNode(101), IntNode(102)) 26 | g.edges.size == (g.addEdge(edge).edges.size - 1) 27 | } 28 | } 29 | } 30 | test("should ignore addition of a duplicated edge") { 31 | check { 32 | forAll(GraphGenerator.graphGen) { (g: IntegerImmutableGraph) => 33 | val duplicatedEdge = g.edges.head 34 | g.nodes.size == g.addEdge(duplicatedEdge).nodes.size 35 | } 36 | } 37 | check { 38 | forAll(GraphGenerator.graphGen) { (g: IntegerImmutableGraph) => 39 | val duplicatedEdge = g.edges.head 40 | g.edges.size == g.addEdge(duplicatedEdge).edges.size 41 | } 42 | } 43 | } 44 | test("replaceNode()") { 45 | val graph: IntegerImmutableGraph = 46 | """digraph yoyak { 47 | | 1; 48 | | 2; 49 | | 3; 50 | | 1 -> 2; 51 | | 2 -> 3; 52 | | 3 -> 1; 53 | |} 54 | """.stripMargin 55 | val newGraph = graph.replaceNode(IntNode(2), IntNode(4)) 56 | newGraph.nodes should be(Set(IntNode(1), IntNode(3), IntNode(4))) 57 | newGraph.edges should be( 58 | Set( 59 | IntEdge(IntNode(1), IntNode(4)), 60 | IntEdge(IntNode(4), IntNode(3)), 61 | IntEdge(IntNode(3), IntNode(1)) 62 | ) 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/graph/ImmutableGraphLikeTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import com.simplytyped.yoyak.graph.GraphGenerator.IntegerImmutableGraph 6 | 7 | /** Created by ihji on 3/24/14. 8 | */ 9 | class ImmutableGraphLikeTest extends AnyFunSuite with Matchers { 10 | test("basic graph construction") { 11 | val graph: IntegerImmutableGraph = 12 | """digraph yoyak { 13 | | 1; 14 | | 2; 15 | | 3; 16 | | 1 -> 2; 17 | | 2 -> 3; 18 | | 3 -> 1; 19 | |} 20 | """.stripMargin 21 | 22 | graph.nodes.size should be(3) 23 | graph.edges.size should be(3) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/graph/IntegerGraphParser.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph 2 | 3 | import scala.util.parsing.combinator.JavaTokenParsers 4 | import com.simplytyped.yoyak.graph.GraphGenerator.{ 5 | IntEdge, 6 | IntNode, 7 | IntegerImmutableGraph 8 | } 9 | 10 | class IntegerGraphParser extends JavaTokenParsers { 11 | def graph: Parser[IntegerImmutableGraph] = 12 | "digraph yoyak {" ~> rep(node) ~ rep(edge) <~ "}" ^^ { case nodes ~ edges => 13 | val graph = IntegerImmutableGraph.empty 14 | val nodeAdded = nodes.foldLeft(graph) { _.addNode(_) } 15 | edges.foldLeft(nodeAdded) { _.addEdge(_) } 16 | } 17 | def node: Parser[IntNode] = 18 | decimalNumber <~ ";" ^^ { x => IntNode(x.toInt) } 19 | def edge: Parser[IntEdge] = 20 | decimalNumber ~ "->" ~ decimalNumber ~ ";" ^^ { case from ~ _ ~ to ~ _ => 21 | IntEdge(IntNode(from.toInt), IntNode(to.toInt)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/graph/algo/GraphRefactoringTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph.algo 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import com.simplytyped.yoyak.graph.GraphGenerator.{ 6 | IntEdge, 7 | IntegerImmutableGraph, 8 | IntNode 9 | } 10 | 11 | class GraphRefactoringTest extends AnyFunSuite with Matchers { 12 | test("merge singly paired nodes") { 13 | val graph: IntegerImmutableGraph = 14 | """digraph yoyak { 15 | | 1 -> 2; 16 | | 2 -> 3; 17 | | 3 -> 4; 18 | | 4 -> 5; 19 | | 4 -> 6; 20 | | 5 -> 7; 21 | | 6 -> 7; 22 | | 7 -> 8; 23 | | 8 -> 9; 24 | | 9 -> 10; 25 | | 10 -> 8; 26 | |} 27 | """.stripMargin 28 | val resultGraph: IntegerImmutableGraph = 29 | """digraph yoyak { 30 | | 1 -> 5; 31 | | 1 -> 6; 32 | | 5 -> 7; 33 | | 6 -> 7; 34 | | 7 -> 8; 35 | | 8 -> 8; 36 | |} 37 | """.stripMargin 38 | val merger = (x: IntNode, y: IntNode) => if (x.data < y.data) x else y 39 | val refactor = 40 | new GraphRefactoringImpl[IntNode, IntEdge, IntegerImmutableGraph] {} 41 | val refactoredGraph = refactor.mergePairedNodes(merger)(graph) 42 | refactoredGraph should be(resultGraph) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/graph/algo/GraphTraverseTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.graph.algo 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import com.simplytyped.yoyak.graph.GraphGenerator.{ 6 | IntEdge, 7 | IntNode, 8 | IntegerImmutableGraph 9 | } 10 | 11 | class GraphTraverseTest extends AnyFunSuite with Matchers { 12 | test("depth first traverse") { 13 | val graph: IntegerImmutableGraph = 14 | """digraph yoyak { 15 | | 1 -> 2; 16 | | 2 -> 3; 17 | | 3 -> 4; 18 | | 4 -> 5; 19 | | 4 -> 6; 20 | | 5 -> 7; 21 | | 6 -> 7; 22 | | 7 -> 8; 23 | | 8 -> 9; 24 | | 9 -> 10; 25 | | 10 -> 8; 26 | |} 27 | """.stripMargin 28 | val traverser = 29 | new GraphTraverseImpl[IntNode, IntEdge, IntegerImmutableGraph] {} 30 | val traverseStream = traverser.depthFirstTraverse(graph) 31 | traverseStream.toList.map { _.data } should be( 32 | List(1, 2, 3, 4, 5, 7, 8, 9, 10, 6) 33 | ) 34 | } 35 | test("find loopheads: Simple") { 36 | val graph: IntegerImmutableGraph = 37 | """digraph yoyak { 38 | | 1 -> 2; 39 | | 2 -> 3; 40 | | 3 -> 4; 41 | | 4 -> 5; 42 | | 4 -> 6; 43 | | 5 -> 7; 44 | | 6 -> 7; 45 | | 7 -> 8; 46 | | 8 -> 9; 47 | | 9 -> 10; 48 | | 10 -> 8; 49 | |} 50 | """.stripMargin 51 | val traverser = 52 | new GraphTraverseImpl[IntNode, IntEdge, IntegerImmutableGraph] {} 53 | val loopHeads = traverser.findLoopheads(graph) 54 | loopHeads.map { _.data }.toSet should be(Set(8)) 55 | } 56 | test("find loopheads: Nested") { 57 | val graph: IntegerImmutableGraph = 58 | """digraph yoyak { 59 | | 1 -> 2; 60 | | 2 -> 3; 61 | | 3 -> 4; 62 | | 4 -> 5; 63 | | 5 -> 6; 64 | | 6 -> 7; 65 | | 7 -> 8; 66 | | 8 -> 9; 67 | | 9 -> 10; 68 | | 9 -> 2; 69 | | 8 -> 3; 70 | | 7 -> 4; 71 | |} 72 | """.stripMargin 73 | val traverser = 74 | new GraphTraverseImpl[IntNode, IntEdge, IntegerImmutableGraph] {} 75 | val loopHeads = traverser.findLoopheads(graph) 76 | loopHeads.map { _.data }.toSet should be(Set(4, 3, 2)) 77 | } 78 | test("find loopheads: Break") { 79 | val graph: IntegerImmutableGraph = 80 | """digraph yoyak { 81 | | 1 -> 2; 82 | | 2 -> 3; 83 | | 3 -> 4; 84 | | 4 -> 5; 85 | | 5 -> 6; 86 | | 6 -> 7; 87 | | 7 -> 8; 88 | | 8 -> 9; 89 | | 9 -> 10; 90 | | 9 -> 2; 91 | | 8 -> 3; 92 | | 4 -> 10; 93 | |} 94 | """.stripMargin 95 | val traverser = 96 | new GraphTraverseImpl[IntNode, IntEdge, IntegerImmutableGraph] {} 97 | val loopHeads = traverser.findLoopheads(graph) 98 | loopHeads.map { _.data }.toSet should be(Set(3, 2)) 99 | } 100 | test("find loopheads: Loop in branch") { 101 | val graph: IntegerImmutableGraph = 102 | """digraph yoyak { 103 | | 1 -> 2; 104 | | 2 -> 3; 105 | | 3 -> 4; 106 | | 4 -> 5; 107 | | 5 -> 6; 108 | | 6 -> 7; 109 | | 4 -> 8; 110 | | 8 -> 9; 111 | | 9 -> 10; 112 | | 7 -> 11; 113 | | 10 -> 11; 114 | | 10 -> 8; 115 | |} 116 | """.stripMargin 117 | val traverser = 118 | new GraphTraverseImpl[IntNode, IntEdge, IntegerImmutableGraph] {} 119 | val loopHeads = traverser.findLoopheads(graph) 120 | loopHeads.map { _.data }.toSet should be(Set(8)) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/parser/cil/CommonILParserTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.cil 2 | 3 | import com.simplytyped.yoyak.il.PrettyPrinter 4 | import org.scalatest.funsuite.AnyFunSuite 5 | import org.scalatest.matchers.should.Matchers 6 | 7 | class CommonILParserTest extends AnyFunSuite with Matchers { 8 | test("assign statement") { 9 | val parser = new CommonILParser 10 | val result = parser.parseAll(parser.clazz, CommonILParserTest.inputPgm) 11 | val clazz = new CommonILTransform().transform(result.get) 12 | println(new PrettyPrinter().toString(clazz)) 13 | } 14 | } 15 | 16 | object CommonILParserTest { 17 | val inputPgm = 18 | """ 19 | | testclass { 20 | | foo(x: int, y: string) { 21 | | a = x; 22 | | b = y; 23 | | bar(b); 24 | | return b; 25 | | } 26 | | bar(a: string) { 27 | | x = a; 28 | | if(x > 0) { 29 | | return 1; 30 | | } else { 31 | | return 0; 32 | | } 33 | | } 34 | | } 35 | """.stripMargin 36 | } 37 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/parser/dex/DexlibDexTransformerTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.dex 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class DexlibDexTransformerTest extends AnyFunSuite with Matchers { 7 | test("simple transformation: sample-app.apk") { 8 | val dexFile = DexlibDexParser.loadDexFile("test/apk/sample-app.apk") 9 | val pgm = (new DexlibDexTransformer).translate(dexFile) 10 | 11 | import com.simplytyped.yoyak.il.cfg.CommonILToCFG 12 | import com.simplytyped.yoyak.analysis.StringAnalysis 13 | import com.simplytyped.yoyak.il.PrettyPrinter 14 | val mtd = pgm.findByMethodName("argTest").head 15 | val cfg = new CommonILToCFG().transform(mtd) 16 | //println(new PrettyPrinter().toString(mtd)) 17 | 18 | val result = new StringAnalysis(cfg).run() 19 | 20 | //println(result) 21 | 22 | pgm.findByMethodName("argTest").nonEmpty should be(true) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/parser/java/AntlrJavaParserTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.java 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class AntlrJavaParserTest extends AnyFunSuite with Matchers { 7 | import AntlrTestHelper._ 8 | test("can parser basic java file") { 9 | val parser = new AntlrJavaParser 10 | val unit = parser.parse(toStream(simple)) 11 | 12 | unit.toStringTree(parser.rawParser.get) should be( 13 | "(compilationUnit (typeDeclaration (classDeclaration class Test (classBody { (classBodyDeclaration (memberDeclaration (methodDeclaration void foo (formalParameters ( )) (methodBody (block { (blockStatement (statement (statementExpression (expression (expression (expression (expression (primary System)) . out) . println) ( (expressionList (expression (primary (literal \"hello\")))) ))) ;)) }))))) }))) )" 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/parser/java/AntlrJavaTransformerTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.java 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class AntlrJavaTransformerTest extends AnyFunSuite with Matchers { 7 | import AntlrTestHelper._ 8 | ignore("basic transform test") { 9 | val parser = new AntlrJavaParser 10 | val unit = parser.parse(toStream(simpleWithParam)) 11 | 12 | val program = (new AntlrJavaTransformer).compilationUnitToProgram(unit) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/parser/java/AntlrTestHelper.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.parser.java 2 | 3 | import java.io.{ByteArrayInputStream, InputStream} 4 | 5 | object AntlrTestHelper { 6 | val simple = 7 | """ 8 | |class Test { 9 | | void foo() { 10 | | System.out.println("hello"); 11 | | } 12 | |} 13 | """.stripMargin 14 | val simpleWithParam = 15 | """ 16 | |class Test { 17 | | void foo(int[][] x) { 18 | | System.out.println("hello"); 19 | | } 20 | |} 21 | """.stripMargin 22 | def toStream(str: String): InputStream = 23 | new ByteArrayInputStream(str.getBytes) 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/solver/algo/BCPTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.algo 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import com.simplytyped.yoyak.solver.domain._ 6 | 7 | /** Created with IntelliJ IDEA. 8 | * User: ihji 9 | * Date: 5/13/12 10 | * Time: 8:57 PM 11 | * To change this template use File | Settings | File Templates. 12 | */ 13 | 14 | class BCPTest extends AnyFunSuite with Matchers { 15 | test("simple BCP") { 16 | import com.simplytyped.yoyak.solver.domain.CNFConversions._ 17 | val cnf: CNF = 18 | """p cnf 3 3 19 | |1 0 20 | |1 2 0 21 | |-1 3 0 22 | """.stripMargin 23 | val bcp = new BCP(cnf) 24 | val cnf$ = bcp.run(PAssign.empty) 25 | cnf$ should be(PAssign(MapDom(Map(1 -> T, 3 -> T)))) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/solver/algo/CDCLTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.algo 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import com.simplytyped.yoyak.solver.domain.{CNF, PAssign} 6 | 7 | /** Created with IntelliJ IDEA. 8 | * User: ihji 9 | * Date: 5/14/12 10 | * Time: 6:16 PM 11 | * To change this template use File | Settings | File Templates. 12 | */ 13 | 14 | class CDCLTest extends AnyFunSuite with Matchers { 15 | test("simple CDCL") { 16 | import com.simplytyped.yoyak.solver.domain.CNFConversions._ 17 | import com.simplytyped.yoyak.solver.domain.PAssign.str2PAssign 18 | val solution: PAssign = "110" 19 | val cnf: CNF = 20 | """p cnf 3 4 21 | |1 0 22 | |1 2 0 23 | |-1 2 3 0 24 | |-3 0 25 | """.stripMargin 26 | val cdcl = new CDCL(cnf) 27 | val cnf$ = cdcl.run(List(PAssign.empty)) 28 | cnf$.get should be(solution) 29 | } 30 | test("unsat CDCL") { 31 | import com.simplytyped.yoyak.solver.domain.CNFConversions._ 32 | val cnf: CNF = 33 | """p cnf 3 4 34 | |1 0 35 | |-1 2 0 36 | |-1 -2 3 0 37 | |-3 0 38 | """.stripMargin 39 | val cdcl = new CDCL(cnf) 40 | val cnf$ = cdcl.run(List(PAssign.empty)) 41 | cnf$ should be(None) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/solver/algo/QCPTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.algo 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | import com.simplytyped.yoyak.solver.domain.{PAssign, CNF} 6 | 7 | /** Created with IntelliJ IDEA. 8 | * User: ihji 9 | * Date: 5/14/12 10 | * Time: 3:32 AM 11 | * To change this template use File | Settings | File Templates. 12 | */ 13 | 14 | class QCPTest extends AnyFunSuite with Matchers { 15 | test("simple QCP") { 16 | import com.simplytyped.yoyak.solver.domain.CNFConversions._ 17 | import com.simplytyped.yoyak.solver.domain.PAssign.str2PAssign 18 | val solution: PAssign = "110" 19 | val cnf: CNF = 20 | """p cnf 3 4 21 | |1 0 22 | |1 2 0 23 | |-1 2 3 0 24 | |-3 0 25 | """.stripMargin 26 | val qcp = new QCP(cnf) 27 | val cnf$ = qcp.run(List(PAssign.empty)) 28 | cnf$.get should be(solution) 29 | } 30 | test("yet another simple QCP") { 31 | import com.simplytyped.yoyak.solver.domain.CNFConversions._ 32 | import com.simplytyped.yoyak.solver.domain.PAssign.str2PAssign 33 | val solution: PAssign = "00101111011101001101111111001011100011111111110000" 34 | val cnf: CNF = 35 | """c instance by G3 (http://www.is.titech.ac.jp/~watanabe/gensat/a1/index.html) 36 | |c solution = 00101111011101001101111111001011100011111111110000 37 | |p cnf 50 230 38 | |-21 20 -35 0 39 | |-36 43 -22 0 40 | |-13 -11 -5 0 41 | |-16 -4 -7 0 42 | |6 -39 47 0 43 | |-33 3 1 0 44 | |-12 31 7 0 45 | |2 -36 48 0 46 | |-14 19 44 0 47 | |22 1 36 0 48 | |-49 -23 4 0 49 | |-10 26 -18 0 50 | |44 -3 50 0 51 | |-6 29 31 0 52 | |24 2 -38 0 53 | |47 23 -26 0 54 | |-24 32 43 0 55 | |11 -22 -31 0 56 | |-31 7 34 0 57 | |5 33 37 0 58 | |3 -10 -29 0 59 | |-48 -45 -3 0 60 | |-5 46 -45 0 61 | |33 -1 15 0 62 | |16 -9 32 0 63 | |-7 -14 39 0 64 | |44 -12 -28 0 65 | |-22 -2 -37 0 66 | |-25 -22 20 0 67 | |38 -5 40 0 68 | |4 -22 25 0 69 | |17 -8 -10 0 70 | |34 -50 -33 0 71 | |-16 47 34 0 72 | |-13 23 6 0 73 | |48 49 -15 0 74 | |13 50 10 0 75 | |-48 -41 -46 0 76 | |-32 13 12 0 77 | |33 24 -15 0 78 | |40 -33 32 0 79 | |-21 38 -41 0 80 | |24 7 27 0 81 | |16 -35 -33 0 82 | |19 15 -4 0 83 | |32 -7 50 0 84 | |21 -36 4 0 85 | |48 -28 -44 0 86 | |42 -8 2 0 87 | |-10 26 -3 0 88 | |-50 48 28 0 89 | |-2 -35 -37 0 90 | |14 -16 15 0 91 | |-32 -7 23 0 92 | |5 -3 33 0 93 | |24 -11 10 0 94 | |12 -34 3 0 95 | |-23 41 -14 0 96 | |-49 34 15 0 97 | |-17 -31 3 0 98 | |13 22 4 0 99 | |19 40 27 0 100 | |8 -32 -39 0 101 | |-30 48 12 0 102 | |25 -30 -40 0 103 | |-30 -16 31 0 104 | |20 -2 27 0 105 | |6 37 50 0 106 | |-23 45 20 0 107 | |17 50 9 0 108 | |-26 -30 -12 0 109 | |-15 27 -4 0 110 | |18 -48 -44 0 111 | |9 -26 37 0 112 | |1 37 11 0 113 | |-21 8 36 0 114 | |12 -4 -41 0 115 | |8 -3 -29 0 116 | |-5 30 32 0 117 | |7 16 -6 0 118 | |29 37 -17 0 119 | |-5 -1 2 0 120 | |40 -15 -3 0 121 | |-12 35 11 0 122 | |-26 49 45 0 123 | |-1 -31 -3 0 124 | |-31 -4 -3 0 125 | |24 2 -44 0 126 | |-39 43 25 0 127 | |-13 -37 -7 0 128 | |-45 -19 -14 0 129 | |49 -29 25 0 130 | |-25 19 -16 0 131 | |-13 -35 -9 0 132 | |4 41 -9 0 133 | |-15 13 -24 0 134 | |-13 10 -20 0 135 | |49 23 46 0 136 | |29 -41 -38 0 137 | |-8 -9 -3 0 138 | |36 33 -20 0 139 | |9 -4 38 0 140 | |16 -22 12 0 141 | |-30 -28 36 0 142 | |16 43 -39 0 143 | |40 16 17 0 144 | |-34 2 -17 0 145 | |50 -36 12 0 146 | |20 31 47 0 147 | |-13 17 38 0 148 | |-22 -48 19 0 149 | |-34 -32 -45 0 150 | |39 33 15 0 151 | |24 -8 33 0 152 | |24 -3 -21 0 153 | |27 1 32 0 154 | |-44 -6 29 0 155 | |-25 -50 -5 0 156 | |5 28 1 0 157 | |-37 34 22 0 158 | |34 -10 -19 0 159 | |7 -22 -34 0 160 | |15 -21 -47 0 161 | |-32 -11 42 0 162 | |33 -46 -48 0 163 | |43 -29 -9 0 164 | |-10 25 -50 0 165 | |-29 -1 -35 0 166 | |-18 43 23 0 167 | |-28 -25 14 0 168 | |-29 -50 -5 0 169 | |-2 29 -32 0 170 | |-42 -8 14 0 171 | |-37 -43 40 0 172 | |-8 -4 -11 0 173 | |29 -40 9 0 174 | |35 7 33 0 175 | |-1 10 18 0 176 | |-41 25 -49 0 177 | |36 18 -38 0 178 | |24 -41 26 0 179 | |7 4 49 0 180 | |11 -36 2 0 181 | |21 32 28 0 182 | |-35 7 31 0 183 | |6 13 36 0 184 | |-20 -15 -25 0 185 | |-11 16 37 0 186 | |-13 34 47 0 187 | |19 46 -47 0 188 | |4 20 -41 0 189 | |21 -6 48 0 190 | |-6 -37 41 0 191 | |9 -26 23 0 192 | |43 9 50 0 193 | |41 17 -16 0 194 | |21 23 -20 0 195 | |-18 -49 -24 0 196 | |-50 46 25 0 197 | |-14 -9 47 0 198 | |-28 45 6 0 199 | |-32 -7 38 0 200 | |-38 -36 10 0 201 | |37 46 15 0 202 | |39 16 -43 0 203 | |45 44 -15 0 204 | |-14 19 10 0 205 | |24 23 39 0 206 | |-21 -7 -36 0 207 | |-6 -50 15 0 208 | |-30 -36 -26 0 209 | |-30 13 -28 0 210 | |22 8 28 0 211 | |43 49 12 0 212 | |31 4 -44 0 213 | |-47 46 -38 0 214 | |-13 -1 -17 0 215 | |26 9 -29 0 216 | |18 2 -20 0 217 | |-17 -40 -2 0 218 | |-32 -17 11 0 219 | |-27 -31 47 0 220 | |1 -28 40 0 221 | |-31 42 -3 0 222 | |-6 -12 25 0 223 | |21 30 14 0 224 | |-42 40 3 0 225 | |25 -23 41 0 226 | |-45 42 10 0 227 | |-29 40 -8 0 228 | |-50 37 -46 0 229 | |-44 46 30 0 230 | |-47 -20 -35 0 231 | |44 -24 48 0 232 | |-41 16 45 0 233 | |1 13 14 0 234 | |11 31 34 0 235 | |-33 -8 45 0 236 | |-38 6 13 0 237 | |48 -22 -30 0 238 | |33 -12 7 0 239 | |32 -29 -25 0 240 | |-2 -43 -36 0 241 | |32 38 -36 0 242 | |35 -4 -45 0 243 | |-18 -12 -47 0 244 | |-44 -31 33 0 245 | |19 -45 -4 0 246 | |-31 28 -35 0 247 | |-48 -14 43 0 248 | |23 16 41 0 249 | |-2 3 -4 0 250 | |-23 33 -31 0 251 | |-22 2 26 0 252 | |-45 -16 -36 0 253 | |-27 -2 7 0 254 | |-29 -45 -28 0 255 | |-3 -37 -47 0 256 | |-33 -27 1 0 257 | |-24 -33 21 0 258 | |-29 -46 -16 0 259 | |50 -13 17 0 260 | |-2 -4 -31 0 261 | |-13 -37 28 0 262 | |14 15 -29 0 263 | |-27 -38 -3 0 264 | |-20 -40 23 0 265 | |-39 -1 26 0 266 | |50 5 -18 0 267 | |-7 27 31 0 268 | """.stripMargin 269 | val qcp = new QCP(cnf) 270 | val cnf$ = qcp.run(List(PAssign.empty)) 271 | cnf$.get should be(solution) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/test/scala/com/simplytyped/yoyak/solver/domain/DIMACSParserTest.scala: -------------------------------------------------------------------------------- 1 | package com.simplytyped.yoyak.solver.domain 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | /** Created with IntelliJ IDEA. 7 | * User: ihji 8 | * Date: 5/13/12 9 | * Time: 5:33 PM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | 13 | class DIMACSParserTest extends AnyFunSuite with Matchers { 14 | test("basic DIMACS parsing") { 15 | val parser = new DIMACSParser {} 16 | val file = 17 | """c this is comment. 18 | |c this is another comment. 19 | |p cnf 5 3 20 | |1 -5 4 0 21 | |-1 5 3 4 0 22 | |-3 -4 0 23 | """.stripMargin 24 | val result = parser.parseAll(parser.cnf, file) 25 | result.successful should be(true) 26 | result.get should be( 27 | Some( 28 | CNF( 29 | 5, 30 | 3, 31 | List( 32 | Clause(List(1, -5, 4)), 33 | Clause(List(-1, 5, 3, 4)), 34 | Clause(List(-3, -4)) 35 | ) 36 | ) 37 | ) 38 | ) 39 | } 40 | test("semantic error DIMACS parsing") { 41 | val parser = new DIMACSParser {} 42 | val file = 43 | """p cnf 5 3 44 | |1 -5 4 0 45 | |-1 6 3 4 0 46 | |-3 -4 0 47 | """.stripMargin 48 | val result = parser.parseAll(parser.cnf, file) 49 | result.successful should be(true) 50 | result.get should be(None) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/apk/pp2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihji/yoyak/428b86024d9bd88cb5c27bd3693256df87093b19/test/apk/pp2.apk -------------------------------------------------------------------------------- /test/apk/sample-app.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihji/yoyak/428b86024d9bd88cb5c27bd3693256df87093b19/test/apk/sample-app.apk -------------------------------------------------------------------------------- /test/apk/sin.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihji/yoyak/428b86024d9bd88cb5c27bd3693256df87093b19/test/apk/sin.apk --------------------------------------------------------------------------------