From e88bb9dc7058d0730c68f107e7c2753b633685f6 Mon Sep 17 00:00:00 2001 From: Fabian Yamaguchi Date: Wed, 22 Jul 2020 18:58:50 +0200 Subject: [PATCH] CfgCreationPass (#211) * Add CfgCreationPass and tests * Address review --- .../fuzzyc2cpg/passes/CfgCreationPass.scala | 434 +++++++++++++++++ .../passes/CfgCreationPassTests.scala | 453 ++++++++++++++++++ 2 files changed, 887 insertions(+) create mode 100644 src/main/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPass.scala create mode 100644 src/test/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPassTests.scala diff --git a/src/main/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPass.scala b/src/main/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPass.scala new file mode 100644 index 0000000..248805f --- /dev/null +++ b/src/main/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPass.scala @@ -0,0 +1,434 @@ +package io.shiftleft.fuzzyc2cpg.passes + +import io.shiftleft.codepropertygraph.Cpg +import io.shiftleft.codepropertygraph.generated.nodes.Call +import io.shiftleft.passes.{DiffGraph, IntervalKeyPool, ParallelCpgPass} +import io.shiftleft.codepropertygraph.generated.{EdgeTypes, Operators, nodes} +import io.shiftleft.fuzzyc2cpg.adapter.{AlwaysEdge, CaseEdge, CfgEdgeType, FalseEdge, TrueEdge} +import io.shiftleft.fuzzyc2cpg.cfg.LayeredStack +import io.shiftleft.semanticcpg.language._ +import org.slf4j.LoggerFactory + +class CfgCreationPass(cpg: Cpg, keyPool: IntervalKeyPool) + extends ParallelCpgPass[nodes.Method](cpg, keyPools = Some(keyPool.split(cpg.method.size))) { + + override def partIterator: Iterator[nodes.Method] = cpg.method.iterator + + override def runOnPart(method: nodes.Method): Iterator[DiffGraph] = + new CfgCreatorForMethod(method).run() + +} + +class CfgCreatorForMethod(entryNode: nodes.Method) { + + private implicit class FringeWrapper(fringe: List[FringeElement]) { + def setCfgEdgeType(cfgEdgeType: CfgEdgeType): List[FringeElement] = { + fringe.map { + case FringeElement(node, _) => + FringeElement(node, cfgEdgeType) + } + } + def add(node: nodes.CfgNode, cfgEdgeType: CfgEdgeType): List[FringeElement] = + FringeElement(node, cfgEdgeType) :: fringe + + def add(ns: List[nodes.CfgNode], cfgEdgeType: CfgEdgeType): List[FringeElement] = + ns.map(node => FringeElement(node, cfgEdgeType)) ++ fringe + + def add(otherFringe: List[FringeElement]): List[FringeElement] = + otherFringe ++ fringe + } + + private val logger = LoggerFactory.getLogger(getClass) + val diffGraph: DiffGraph.Builder = DiffGraph.newBuilder + + private var fringe = List[FringeElement]().add(entryNode, AlwaysEdge) + private var markerStack = List[Option[nodes.CfgNode]]() + private case class FringeElement(node: nodes.CfgNode, cfgEdgeType: CfgEdgeType) + private var labeledNodes = Map[String, nodes.CfgNode]() + private var pendingGotoLabels = List[String]() + private var pendingCaseLabels = List[String]() + private var returns = List[nodes.CfgNode]() + private val breakStack = new LayeredStack[nodes.CfgNode]() + private val continueStack = new LayeredStack[nodes.CfgNode]() + private val caseStack = new LayeredStack[(nodes.CfgNode, Boolean)]() + private var gotos = List[(nodes.CfgNode, String)]() + + def run(): Iterator[DiffGraph] = { + postOrderLeftToRightExpand(entryNode) + connectGotosAndLabels() + connectReturnsToExit() + Iterator(diffGraph.build) + } + + private def postOrderLeftToRightExpand(node: nodes.AstNode): Unit = { + node match { + case n: nodes.ControlStructure => + handleControlStructure(n) + case n: nodes.JumpTarget => + handleJumpTarget(n) + case call: nodes.Call if call.name == Operators.conditional => + handleConditionalExpression(call) + case call: nodes.Call if call.name == Operators.logicalAnd => + handleAndExpression(call) + case call: nodes.Call if call.name == Operators.logicalOr => + handleOrExpression(call) + case call: nodes.Call => + handleCall(call) + case identifier: nodes.Identifier => + handleIdentifier(identifier) + case literal: nodes.Literal => + handleLiteral(literal) + case actualRet: nodes.Return => + handleReturn(actualRet) + case formalRet: nodes.MethodReturn => + handleFormalReturn(formalRet) + case n: nodes.AstNode => + expandChildren(n) + } + } + + private def handleCall(call: nodes.Call): Unit = { + expandChildren(call) + extendCfg(call) + } + + private def handleIdentifier(identifier: nodes.Identifier): Unit = { + extendCfg(identifier) + } + + private def handleLiteral(literal: nodes.Literal): Unit = { + extendCfg(literal) + } + + private def handleReturn(actualRet: nodes.Return): Unit = { + expandChildren(actualRet) + extendCfg(actualRet) + fringe = Nil + returns = actualRet :: returns + } + + private def handleFormalReturn(formalRet: nodes.MethodReturn): Unit = { + extendCfg(formalRet) + } + + private def connectGotosAndLabels(): Unit = { + gotos.foreach { + case (goto, label) => + labeledNodes.get(label) match { + case Some(labeledNode) => + // TODO: CFG_EDGE_TYPE isn't defined for non-proto CPGs + // .addProperty(EdgeProperty.CFG_EDGE_TYPE, AlwaysEdge.toString) + diffGraph.addEdge( + goto, + labeledNode, + EdgeTypes.CFG + ) + case None => + logger.info("Unable to wire goto statement. Missing label {}.", label) + } + } + } + + private def connectReturnsToExit(): Unit = { + returns.foreach( + diffGraph.addEdge( + _, + entryNode.methodReturn, + EdgeTypes.CFG + ) + ) + } + + private def handleJumpTarget(n: nodes.JumpTarget): Unit = { + val labelName = n.name + if (labelName.startsWith("case") || labelName.startsWith("default")) { + pendingCaseLabels = labelName :: pendingCaseLabels + } else { + pendingGotoLabels = labelName :: pendingGotoLabels + } + } + + private def handleConditionalExpression(call: nodes.Call): Unit = { + val condition = call.argument(1) + val trueExpression = call.argument(2) + val falseExpression = call.argument(3) + + postOrderLeftToRightExpand(condition) + val fromCond = fringe + fringe = fringe.setCfgEdgeType(TrueEdge) + postOrderLeftToRightExpand(trueExpression) + val fromTrue = fringe + fringe = fromCond.setCfgEdgeType(FalseEdge) + postOrderLeftToRightExpand(falseExpression) + fringe = fringe.add(fromTrue) + extendCfg(call) + } + + private def handleAndExpression(call: Call): Unit = { + postOrderLeftToRightExpand(call.argument(1)) + val entry = fringe + fringe = fringe.setCfgEdgeType(TrueEdge) + postOrderLeftToRightExpand(call.argument(2)) + fringe = fringe.add(entry.setCfgEdgeType(FalseEdge)) + extendCfg(call) + } + + private def handleOrExpression(call: Call): Unit = { + val left = call.argument(1) + val right = call.argument(2) + postOrderLeftToRightExpand(left) + val entry = fringe + fringe = fringe.setCfgEdgeType(FalseEdge) + postOrderLeftToRightExpand(right) + fringe = fringe.add(entry.setCfgEdgeType(TrueEdge)) + extendCfg(call) + } + + private def handleBreakStatement(node: nodes.ControlStructure): Unit = { + extendCfg(node) + // Under normal conditions this is always true. + // But if the parser missed a loop or switch statement, breakStack + // might by empty. + if (breakStack.numberOfLayers > 0) { + fringe = Nil + breakStack.store(node) + } + } + + private def handleContinueStatement(node: nodes.ControlStructure): Unit = { + extendCfg(node) + // Under normal conditions this is always true. + // But if the parser missed a loop statement, continueStack + // might by empty. + if (continueStack.numberOfLayers > 0) { + fringe = Nil + continueStack.store(node) + } + } + + private def handleWhileStatement(node: nodes.ControlStructure): Unit = { + breakStack.pushLayer() + continueStack.pushLayer() + + markerStack = None :: markerStack + node.start.condition.headOption.foreach(postOrderLeftToRightExpand) + val conditionFringe = fringe + fringe = fringe.setCfgEdgeType(TrueEdge) + + node.start.whenTrue.l.foreach(postOrderLeftToRightExpand) + fringe = fringe.add(continueStack.getTopElements, AlwaysEdge) + extendCfg(markerStack.head.get) + + fringe = conditionFringe + .setCfgEdgeType(FalseEdge) + .add(breakStack.getTopElements, AlwaysEdge) + + markerStack = markerStack.tail + breakStack.popLayer() + continueStack.popLayer() + } + + private def handleDoStatement(node: nodes.ControlStructure): Unit = { + breakStack.pushLayer() + continueStack.pushLayer() + + markerStack = None :: markerStack + node.astChildren.filter(_.order(1)).foreach(postOrderLeftToRightExpand) + fringe = fringe.add(continueStack.getTopElements, AlwaysEdge) + + node.start.condition.headOption match { + case Some(condition) => + postOrderLeftToRightExpand(condition) + val conditionFringe = fringe + fringe = fringe.setCfgEdgeType(TrueEdge) + + extendCfg(markerStack.head.get) + + fringe = conditionFringe.setCfgEdgeType(FalseEdge) + case None => + // We only get here if the parser missed the condition. + // In this case doing nothing here means that we have + // no CFG edge to the loop start because we default + // to an always false condition. + } + fringe = fringe.add(breakStack.getTopElements, AlwaysEdge) + + markerStack = markerStack.tail + breakStack.popLayer() + continueStack.popLayer() + } + + private def handleForStatement(node: nodes.ControlStructure): Unit = { + breakStack.pushLayer() + continueStack.pushLayer() + + val children = node.astChildren.l + val initExprOption = children.find(_.order == 1) + val conditionOption = children.find(_.order == 2) + val loopExprOption = children.find(_.order == 3) + val statementOption = children.find(_.order == 4) + + initExprOption.foreach(postOrderLeftToRightExpand) + + markerStack = None :: markerStack + val conditionFringe = + conditionOption match { + case Some(condition) => + postOrderLeftToRightExpand(condition) + val storedFringe = fringe + fringe = fringe.setCfgEdgeType(TrueEdge) + storedFringe + case None => Nil + } + + statementOption.foreach(postOrderLeftToRightExpand) + + fringe = fringe.add(continueStack.getTopElements, AlwaysEdge) + + loopExprOption.foreach(postOrderLeftToRightExpand) + + markerStack.head.foreach(extendCfg) + + fringe = conditionFringe + .setCfgEdgeType(FalseEdge) + .add(breakStack.getTopElements, AlwaysEdge) + + markerStack = markerStack.tail + breakStack.popLayer() + continueStack.popLayer() + } + + private def handleGotoStatement(node: nodes.ControlStructure): Unit = { + extendCfg(node) + fringe = Nil + // TODO: the target name should be in the AST + node.code.split(" ").lastOption.map(x => x.slice(0, x.length - 1)).foreach { target => + gotos = (node, target) :: gotos + } + } + + private def handleIfStatement(node: nodes.ControlStructure): Unit = { + node.start.condition.foreach(postOrderLeftToRightExpand) + val conditionFringe = fringe + fringe = fringe.setCfgEdgeType(TrueEdge) + node.start.whenTrue.foreach(postOrderLeftToRightExpand) + node.start.whenFalse + .map { elseStatement => + val ifBlockFringe = fringe + fringe = conditionFringe.setCfgEdgeType(FalseEdge) + postOrderLeftToRightExpand(elseStatement) + fringe = fringe.add(ifBlockFringe) + } + .headOption + .getOrElse { + fringe = fringe.add(conditionFringe.setCfgEdgeType(FalseEdge)) + } + } + + private def handleSwitchStatement(node: nodes.ControlStructure): Unit = { + node.start.condition.foreach(postOrderLeftToRightExpand) + val conditionFringe = fringe.setCfgEdgeType(CaseEdge) + fringe = Nil + + // We can only push the break and case stacks after we processed the condition + // in order to allow for nested switches with no nodes CFG nodes in between + // an outer switch case label and the inner switch condition. + // This is ok because in C/C++ it is not allowed to have another switch + // statement in the condition of a switch statement. + breakStack.pushLayer() + caseStack.pushLayer() + + node.start.whenTrue.foreach(postOrderLeftToRightExpand) + val switchFringe = fringe + + caseStack.getTopElements.foreach { + case (caseNode, _) => + fringe = conditionFringe + extendCfg(caseNode) + } + + val hasDefaultCase = caseStack.getTopElements.exists { + case (_, isDefault) => + isDefault + } + + fringe = switchFringe.add(breakStack.getTopElements, AlwaysEdge) + + if (!hasDefaultCase) { + fringe = fringe.add(conditionFringe) + } + + breakStack.popLayer() + caseStack.popLayer() + } + + private def handleControlStructure(node: nodes.ControlStructure): Unit = { + node.parserTypeName match { + case "BreakStatement" => + handleBreakStatement(node) + case "ContinueStatement" => + handleContinueStatement(node) + case "WhileStatement" => + handleWhileStatement(node) + case "DoStatement" => + handleDoStatement(node) + case "ForStatement" => + handleForStatement(node) + case "GotoStatement" => + handleGotoStatement(node) + case "IfStatement" => + handleIfStatement(node) + case "ElseStatement" => + expandChildren(node) + case "SwitchStatement" => + handleSwitchStatement(node) + case _ => + } + } + + private def expandChildren(node: nodes.AstNode): Unit = { + val children = node.astChildren.l + children.foreach(postOrderLeftToRightExpand) + } + + private def extendCfg(dstNode: nodes.CfgNode): Unit = { + fringe.foreach { + case FringeElement(srcNode, _) => + // TODO add edge CFG edge type in CPG spec + // val props = List(("CFG_EDGE_TYPE", cfgEdgeType.toString)) + diffGraph.addEdge( + srcNode, + dstNode, + EdgeTypes.CFG + ) + } + fringe = Nil.add(dstNode, AlwaysEdge) + + if (markerStack.nonEmpty) { + // Up until the first none None stack element we replace the Nones with Some(dstNode) + val leadingNoneLength = markerStack.segmentLength(_.isEmpty, 0) + markerStack = List.fill(leadingNoneLength)(Some(dstNode)) ++ markerStack + .drop(leadingNoneLength) + } + + if (pendingGotoLabels.nonEmpty) { + pendingGotoLabels.foreach { label => + labeledNodes = labeledNodes + (label -> dstNode) + } + pendingGotoLabels = List() + } + + // TODO at the moment we discard the case labels + if (pendingCaseLabels.nonEmpty) { + // Under normal conditions this is always true. + // But if the parser missed a switch statement, caseStack + // might by empty. + if (caseStack.numberOfLayers > 0) { + val containsDefaultLabel = pendingCaseLabels.contains("default") + caseStack.store((dstNode, containsDefaultLabel)) + } + pendingCaseLabels = List() + } + } + +} diff --git a/src/test/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPassTests.scala b/src/test/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPassTests.scala new file mode 100644 index 0000000..3d17e1e --- /dev/null +++ b/src/test/scala/io/shiftleft/fuzzyc2cpg/passes/CfgCreationPassTests.scala @@ -0,0 +1,453 @@ +package io.shiftleft.fuzzyc2cpg.passes + +import better.files.File +import io.shiftleft.codepropertygraph.Cpg +import io.shiftleft.fuzzyc2cpg.adapter.{AlwaysEdge, CaseEdge, CfgEdgeType, FalseEdge, TrueEdge} +import io.shiftleft.passes.IntervalKeyPool +import org.scalatest.{Matchers, WordSpec} +import io.shiftleft.semanticcpg.language._ + +import scala.jdk.CollectionConverters._ +import io.shiftleft.codepropertygraph.generated.nodes + +class CfgCreationPassTests extends WordSpec with Matchers { + + "Cfg" should { + "be correct for decl statement with assignment" in + new CfgFixture("int x = 1;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x = 1", AlwaysEdge)) + succOf("x = 1") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for nested expression" in + new CfgFixture("x = y + 1;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("y + 1", AlwaysEdge)) + succOf("y + 1") shouldBe expected(("x = y + 1", AlwaysEdge)) + succOf("x = y + 1") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for return statement" in + new CfgFixture("return x;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("return x;", AlwaysEdge)) + succOf("return x;") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for consecutive return statements" in + new CfgFixture("return x; return y;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("return x;", AlwaysEdge)) + succOf("y") shouldBe expected(("return y;", AlwaysEdge)) + succOf("return x;") shouldBe expected(("RET", AlwaysEdge)) + succOf("return y;") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for void return statement" in + new CfgFixture("return;") { + succOf("func ()") shouldBe expected(("return;", AlwaysEdge)) + succOf("return;") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for call expression" in + new CfgFixture("foo(a + 1, b);") { + succOf("func ()") shouldBe expected(("a", AlwaysEdge)) + succOf("a") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("a + 1", AlwaysEdge)) + succOf("a + 1") shouldBe expected(("b", AlwaysEdge)) + succOf("b") shouldBe expected(("foo(a + 1, b)", AlwaysEdge)) + succOf("foo(a + 1, b)") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for unary expression '+'" in + new CfgFixture("+x;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("+x", AlwaysEdge)) + succOf("+x") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for unary expression '++'" in + new CfgFixture("++x;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("++x", AlwaysEdge)) + succOf("++x") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for conditional expression" in + new CfgFixture("x ? y : z;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", TrueEdge), ("z", FalseEdge)) + succOf("y") shouldBe expected(("x ? y : z", AlwaysEdge)) + succOf("z") shouldBe expected(("x ? y : z", AlwaysEdge)) + succOf("x ? y : z") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for short-circuit AND expression" in + // TODO: Broken by supporting move params? + new CfgFixture("x && y;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", TrueEdge), ("x && y", FalseEdge)) + succOf("y") shouldBe expected(("x && y", AlwaysEdge)) + succOf("x && y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for short-circuit OR expression" in + new CfgFixture("x || y;") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", FalseEdge), ("x || y", TrueEdge)) + succOf("y") shouldBe expected(("x || y", AlwaysEdge)) + succOf("x || y") shouldBe expected(("RET", AlwaysEdge)) + } + } + + "Cfg for while-loop" should { + "be correct" in + new CfgFixture("while (x < 1) { y = 2; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x < 1", AlwaysEdge)) + succOf("x < 1") shouldBe expected(("y", TrueEdge), ("RET", FalseEdge)) + succOf("y") shouldBe expected(("2", AlwaysEdge)) + succOf("2") shouldBe expected(("y = 2", AlwaysEdge)) + succOf("y = 2") shouldBe expected(("x", AlwaysEdge)) + } + + "be correct with break" in + new CfgFixture("while (x < 1) { break; y; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x < 1", AlwaysEdge)) + succOf("x < 1") shouldBe expected(("break;", TrueEdge), ("RET", FalseEdge)) + succOf("break;") shouldBe expected(("RET", AlwaysEdge)) + succOf("y") shouldBe expected(("x", AlwaysEdge)) + } + + "be correct with continue" in + new CfgFixture("while (x < 1) { continue; y; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x < 1", AlwaysEdge)) + succOf("x < 1") shouldBe expected(("continue;", TrueEdge), ("RET", FalseEdge)) + succOf("continue;") shouldBe expected(("x", AlwaysEdge)) + succOf("y") shouldBe expected(("x", AlwaysEdge)) + } + + "be correct with nested while-loop" in + new CfgFixture("while (x) { while (y) { z; }}") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", TrueEdge), ("RET", FalseEdge)) + succOf("y") shouldBe expected(("z", TrueEdge), ("x", FalseEdge)) + succOf("z") shouldBe expected(("y", AlwaysEdge)) + } + } + + "Cfg for do-while-loop" should { + "be correct" in + new CfgFixture("do { y = 2; } while (x < 1);") { + succOf("func ()") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("2", AlwaysEdge)) + succOf("2") shouldBe expected(("y = 2", AlwaysEdge)) + succOf("y = 2") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x < 1", AlwaysEdge)) + succOf("x < 1") shouldBe expected(("y", TrueEdge), ("RET", FalseEdge)) + } + + "be correct with break" in + new CfgFixture("do { break; y; } while (x < 1);") { + succOf("func ()") shouldBe expected(("break;", AlwaysEdge)) + succOf("break;") shouldBe expected(("RET", AlwaysEdge)) + succOf("y") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x < 1", AlwaysEdge)) + succOf("x < 1") shouldBe expected(("break;", TrueEdge), ("RET", FalseEdge)) + } + + "be correct with continue" in + new CfgFixture("do { continue; y; } while (x < 1);") { + succOf("func ()") shouldBe expected(("continue;", AlwaysEdge)) + succOf("continue;") shouldBe expected(("x", AlwaysEdge)) + succOf("y") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x < 1", AlwaysEdge)) + succOf("x < 1") shouldBe expected(("continue;", TrueEdge), ("RET", FalseEdge)) + } + + "be correct with nested do-while-loop" in + new CfgFixture("do { do { x; } while (y); } while (z);") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("x", TrueEdge), ("z", FalseEdge)) + succOf("z") shouldBe expected(("x", TrueEdge), ("RET", FalseEdge)) + } + } + + "Cfg for for-loop" should { + "be correct" in + new CfgFixture("for (x = 0; y < 1; z += 2) { a = 3; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("0", AlwaysEdge)) + succOf("0") shouldBe expected(("x = 0", AlwaysEdge)) + succOf("x = 0") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("y < 1", AlwaysEdge)) + succOf("y < 1") shouldBe expected(("a", TrueEdge), ("RET", FalseEdge)) + succOf("a") shouldBe expected(("3", AlwaysEdge)) + succOf("3") shouldBe expected(("a = 3", AlwaysEdge)) + succOf("a = 3") shouldBe expected(("z", AlwaysEdge)) + succOf("z") shouldBe expected(("2", AlwaysEdge)) + succOf("2") shouldBe expected(("z += 2", AlwaysEdge)) + succOf("z += 2") shouldBe expected(("y", AlwaysEdge)) + } + + "be correct with break" in + new CfgFixture("for (x = 0; y < 1; z += 2) { break; a = 3; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("0", AlwaysEdge)) + succOf("x = 0") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("y < 1", AlwaysEdge)) + succOf("y < 1") shouldBe expected(("break;", TrueEdge), ("RET", FalseEdge)) + succOf("break;") shouldBe expected(("RET", AlwaysEdge)) + succOf("a") shouldBe expected(("3", AlwaysEdge)) + succOf("3") shouldBe expected(("a = 3", AlwaysEdge)) + succOf("a = 3") shouldBe expected(("z", AlwaysEdge)) + succOf("z") shouldBe expected(("2", AlwaysEdge)) + succOf("2") shouldBe expected(("z += 2", AlwaysEdge)) + succOf("z += 2") shouldBe expected(("y", AlwaysEdge)) + } + + "be correct with continue" in + new CfgFixture("for (x = 0; y < 1; z += 2) { continue; a = 3; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("0", AlwaysEdge)) + succOf("0") shouldBe expected(("x = 0", AlwaysEdge)) + succOf("x = 0") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("y < 1", AlwaysEdge)) + succOf("y < 1") shouldBe expected(("continue;", TrueEdge), ("RET", FalseEdge)) + succOf("continue;") shouldBe expected(("z", AlwaysEdge)) + succOf("a") shouldBe expected(("3", AlwaysEdge)) + succOf("3") shouldBe expected(("a = 3", AlwaysEdge)) + succOf("a = 3") shouldBe expected(("z", AlwaysEdge)) + succOf("z") shouldBe expected(("2", AlwaysEdge)) + succOf("2") shouldBe expected(("z += 2", AlwaysEdge)) + succOf("z += 2") shouldBe expected(("y", AlwaysEdge)) + } + + "be correct with nested for-loop" in + new CfgFixture("for (x; y; z) { for (a; b; c) { u; } }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", AlwaysEdge)) + succOf("y") shouldBe expected(("a", TrueEdge), ("RET", FalseEdge)) + succOf("z") shouldBe expected(("y", AlwaysEdge)) + succOf("a") shouldBe expected(("b", AlwaysEdge)) + succOf("b") shouldBe expected(("u", TrueEdge), ("z", FalseEdge)) + succOf("c") shouldBe expected(("b", AlwaysEdge)) + succOf("u") shouldBe expected(("c", AlwaysEdge)) + } + + "be correct with empty condition" in + new CfgFixture("for (;;) { a = 1; }") { + succOf("func ()") shouldBe expected(("a", AlwaysEdge)) + succOf("a") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("a = 1", AlwaysEdge)) + succOf("a = 1") shouldBe expected(("a", AlwaysEdge)) + } + + "be correct with empty condition with break" in + new CfgFixture("for (;;) { break; }") { + succOf("func ()") shouldBe expected(("break;", AlwaysEdge)) + succOf("break;") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with empty condition with continue" in + new CfgFixture("for (;;) { continue ; }") { + succOf("func ()") shouldBe expected(("continue ;", AlwaysEdge)) + succOf("continue ;") shouldBe expected(("continue ;", AlwaysEdge)) + } + + "be correct with empty condition with nested empty for-loop" in + new CfgFixture("for (;;) { for (;;) { x; } }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("x", AlwaysEdge)) + } + + "be correct with empty condition with empty block" in + new CfgFixture("for (;;) ;") { + succOf("func ()") shouldBe expected() + } + + "be correct when empty for-loop is skipped" in + new CfgFixture("for (;;) {}; return;") { + succOf("func ()") shouldBe expected() + succOf("return;") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with function call condition with empty block" in + new CfgFixture("for (; x(1);) ;") { + succOf("func ()") shouldBe expected(("1", AlwaysEdge)) + succOf("1") shouldBe expected(("x(1)", AlwaysEdge)) + succOf("x(1)") shouldBe expected(("1", TrueEdge), ("RET", FalseEdge)) + } + } + + "Cfg for goto" should { + "be correct for single label" in + new CfgFixture("x; goto l1; y; l1:") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("goto l1;", AlwaysEdge)) + succOf("goto l1;") shouldBe expected(("RET", AlwaysEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for multiple labels" in + new CfgFixture("x;goto l1; l2: y; l1:") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("goto l1;", AlwaysEdge)) + succOf("goto l1;") shouldBe expected(("RET", AlwaysEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for multiple labels on same spot" in + new CfgFixture("x;goto l2;y;l1:l2:") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("goto l2;", AlwaysEdge)) + succOf("goto l2;") shouldBe expected(("RET", AlwaysEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + } + + "Cfg for switch" should { + "be correct with one case" in + new CfgFixture("switch (x) { case 1: y; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge), ("RET", CaseEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with multiple cases" in + new CfgFixture("switch (x) { case 1: y; case 2: z;}") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge), ("z", CaseEdge), ("RET", CaseEdge)) + succOf("y") shouldBe expected(("z", AlwaysEdge)) + succOf("z") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with multiple cases on same spot" in + new CfgFixture("switch (x) { case 1: case 2: y; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge), ("RET", CaseEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with multiple cases and multiple cases on same spot" in + new CfgFixture("switch (x) { case 1: case 2: y; case 3: z;}") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge), ("z", CaseEdge), ("RET", CaseEdge)) + succOf("y") shouldBe expected(("z", AlwaysEdge)) + succOf("z") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with default case" in + new CfgFixture("switch (x) { default: y; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for case and default combined" in + new CfgFixture("switch (x) { case 1: y; break; default: z;}") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge), ("z", CaseEdge)) + succOf("y") shouldBe expected(("break;", AlwaysEdge)) + succOf("break;") shouldBe expected(("RET", AlwaysEdge)) + succOf("z") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct for nested switch" in + new CfgFixture("switch (x) { default: switch(y) { default: z; } }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", CaseEdge)) + succOf("y") shouldBe expected(("z", CaseEdge)) + succOf("z") shouldBe expected(("RET", AlwaysEdge)) + } + } + + "Cfg for if" should { + "be correct" in + new CfgFixture("if (x) { y; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", TrueEdge), ("RET", FalseEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with else block" in + new CfgFixture("if (x) { y; } else { z; }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", TrueEdge), ("z", FalseEdge)) + succOf("y") shouldBe expected(("RET", AlwaysEdge)) + succOf("z") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with nested if" in + new CfgFixture("if (x) { if (y) { z; } }") { + succOf("func ()") shouldBe expected(("x", AlwaysEdge)) + succOf("x") shouldBe expected(("y", TrueEdge), ("RET", FalseEdge)) + succOf("y") shouldBe expected(("z", TrueEdge), ("RET", FalseEdge)) + succOf("z") shouldBe expected(("RET", AlwaysEdge)) + } + + "be correct with else if chain" in + new CfgFixture("if (a) { b; } else if (c) { d;} else { e; }") { + succOf("func ()") shouldBe expected(("a", AlwaysEdge)) + succOf("a") shouldBe expected(("b", TrueEdge), ("c", FalseEdge)) + succOf("b") shouldBe expected(("RET", AlwaysEdge)) + succOf("c") shouldBe expected(("d", TrueEdge), ("e", FalseEdge)) + succOf("d") shouldBe expected(("RET", AlwaysEdge)) + succOf("e") shouldBe expected(("RET", AlwaysEdge)) + } + } + +} + +class CfgFixture(file1Code: String) { + + val cpg: Cpg = Cpg.emptyCpg + + File.usingTemporaryDirectory("fuzzyctest") { dir => + val file1 = (dir / "file1.c") + file1.write(s"int func() { $file1Code }") + val keyPoolFile1 = new IntervalKeyPool(1001, 2000) + val cfgKeyPool = new IntervalKeyPool(2001, 3000) + val filenames = List(file1.path.toAbsolutePath.toString) + new AstCreationPass(filenames, cpg, keyPoolFile1).createAndApply() + new CfgCreationPass(cpg, cfgKeyPool).createAndApply() + } + + val codeToNode: Map[String, nodes.CfgNode] = + cpg.method.ast.isCfgNode.l.map { node => + node.code -> node + }.toMap + + def expected(pairs: (String, CfgEdgeType)*): Set[String] = { + pairs.map { + case (code, cfgEdgeType) => + codeToNode(code).start.code.head + }.toSet + } + + def succOf(code: String): Set[String] = { + codeToNode(code) + ._cfgOut() + .asScala + .map(_.asInstanceOf[nodes.CfgNode]) + .toSet + .map[String](_.code) + } + +}