diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotAstGeneratorTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotAstGeneratorTests.scala index e504e951950e..15399533a725 100644 --- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotAstGeneratorTests.scala +++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotAstGeneratorTests.scala @@ -32,7 +32,7 @@ class DotAstGeneratorTests extends CCodeToCpgSuite { inside(cpg.method.name("my_func").dotAst.l) { case List(x) => x should ( startWith("digraph \"my_func\"") and - include("""[label = <(CONTROL_STRUCTURE,if (y > 42),if (y > 42))5> ]""") and + include("""[label = if (y > 42)> ]""") and endWith("}\n") ) } @@ -52,7 +52,7 @@ class DotAstGeneratorTests extends CCodeToCpgSuite { "allow plotting sub trees of methods" in { inside(cpg.method.ast.isControlStructure.code(".*y > 42.*").dotAst.l) { case List(x, _) => - x should (include("y > 42") and include("IDENTIFIER,y") and not include "x * 2") + x should (include("y > 42") and include("IDENTIFIER, 5
y") and not include "x * 2") } } @@ -60,10 +60,8 @@ class DotAstGeneratorTests extends CCodeToCpgSuite { inside(cpg.method.name("lemon").dotAst.l) { case List(x) => x should ( startWith("digraph \"lemon\"") and - include("""[label = <(goog,goog("\"yes\""))18> ]""") and - include( - """[label = <(LITERAL,"\"yes\"",goog("\"yes\""))18> ]""" - ) and + include("""[label = goog
goog("\"yes\"")> ]""") and + include("""[label = "\"yes\""> ]""") and endWith("}\n") ) } diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCdgGeneratorTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCdgGeneratorTests.scala index 08778bd8ec6e..954e7035e676 100644 --- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCdgGeneratorTests.scala +++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCdgGeneratorTests.scala @@ -19,9 +19,9 @@ class DotCdgGeneratorTests extends DataFlowCodeToCpgSuite { inside(cpg.method.name("foo").dotCdg.l) { case List(x) => x should ( startWith("digraph \"foo\"") and - include("""[label = <(<operator>.greaterThan,x > 8)3> ]""") and - include("""[label = <(<operator>.assignment,z = a(x))4> ]""") and - include("""[label = <(a,a(x))4> ]""") and + include("""[label = <operator>.greaterThan
x > 8> ]""") and + include("""[label = <operator>.assignment
z = a(x)> ]""") and + include("""[label = a
a(x)> ]""") and endWith("}\n") ) val lines = x.split("\n") @@ -46,9 +46,9 @@ class DotCdgGeneratorTests extends DataFlowCodeToCpgSuite { inside(cpg.method.name("foo").dotCdg.l) { case List(x) => x should ( startWith("digraph \"foo\"") and - include("""[label = <(<operator>.greaterThan,x > 8)3> ]""") and - include("""[label = <(<operator>.assignment,z = a(x))4> ]""") and - include("""[label = <(a,a(x))4> ]""") and + include("""[label = <operator>.greaterThan
x > 8> ]""") and + include("""[label = <operator>.assignment
z = a(x)> ]""") and + include("""[label = a
a(x)> ]""") and endWith("}\n") ) val lines = x.split("\n") diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCfgGeneratorTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCfgGeneratorTests.scala index 237944ea4d64..59b06936439c 100644 --- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCfgGeneratorTests.scala +++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/io/dotgenerator/DotCfgGeneratorTests.scala @@ -21,7 +21,7 @@ class DotCfgGeneratorTests extends CCodeToCpgSuite { inside(cpg.method.name("main").dotCfg.l) { case List(dotStr) => dotStr should ( startWith("digraph \"main\" {") and - include("(<operator>.assignment,i = 0)") and + include("<operator>.assignment
i = 0") and endWith("}\n") ) } diff --git a/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/dotgenerator/DotSerializer.scala b/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/dotgenerator/DotSerializer.scala index 88029763adc2..ad5a14041616 100644 --- a/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/dotgenerator/DotSerializer.scala +++ b/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/dotgenerator/DotSerializer.scala @@ -4,10 +4,11 @@ import io.shiftleft.codepropertygraph.generated.PropertyNames import io.shiftleft.codepropertygraph.generated.nodes._ import io.shiftleft.semanticcpg.language._ import io.shiftleft.semanticcpg.utils.MemberAccess +import org.apache.commons.lang.StringUtils -import java.util.Optional import scala.collection.immutable.HashMap import scala.collection.mutable +import scala.jdk.OptionConverters.RichOptional import scala.language.postfixOps object DotSerializer { @@ -36,6 +37,7 @@ object DotSerializer { case Some(r) => namedGraphBegin(r) case None => defaultGraphBegin() } + sb.append("node [shape=\"rect\"];\n") val nodeStrings = graph.vertices.map(nodeToDot) val edgeStrings = graph.edges.map(e => edgeToDot(e, withEdgeTypes)) val subgraphStrings = graph.subgraph.zipWithIndex.map { case ((subgraph, nodes), idx) => @@ -61,23 +63,29 @@ object DotSerializer { } private def stringRepr(vertex: StoredNode): String = { - val maybeLineNo: Optional[AnyRef] = vertex.propertyOption(PropertyNames.LINE_NUMBER) - escape(vertex match { - case call: Call => (call.name, call.code).toString - case expr: Expression => (expr.label, expr.code, toCfgNode(expr).code).toString - case method: Method => (method.label, method.name).toString - case ret: MethodReturn => (ret.label, ret.typeFullName).toString - case param: MethodParameterIn => ("PARAM", param.code).toString - case local: Local => (local.label, s"${local.code}: ${local.typeFullName}").toString - case target: JumpTarget => (target.label, target.name).toString - case modifier: Modifier => (modifier.label, modifier.modifierType).toString() - case annoAssign: AnnotationParameterAssign => (annoAssign.label, annoAssign.code).toString() - case annoParam: AnnotationParameter => (annoParam.label, annoParam.code).toString() - case typ: Type => (typ.label, typ.name).toString() - case typeDecl: TypeDecl => (typeDecl.label, typeDecl.name).toString() - case member: Member => (member.label, member.name).toString() - case _ => "" - }) + (if (maybeLineNo.isPresent) s"${maybeLineNo.get()}" else "") + val lineOpt = vertex.propertyOption(PropertyNames.LINE_NUMBER).toScala.map(_.toString) + val list = (vertex match { + case call: Call => List(call.label, call.name, call.code) + case identifier: Identifier => List(identifier.label, identifier.name) + case literal: Literal => List(literal.label, literal.code) + case local: Local => List(local.label, local.name, local.typeFullName) + case method: Method => List(method.label, method.name) + case expr: Expression => List(expr.label, expr.code, toCfgNode(expr).code) + case ret: MethodReturn => List(ret.label) + case typeDecl: TypeDecl => List(typeDecl.label, typeDecl.name) + case param: MethodParameterIn => List("PARAM", param.name) + case target: JumpTarget => List(target.label, target.name) + case modifier: Modifier => List(modifier.label, modifier.modifierType) + case typ: Type => List(typ.label, typ.name) + case dec: DeclarationBase => List(dec.label, dec.name) + case others: AstNode => List(others.label, others.code) + }).map(StringUtils.normalizeSpace) + .map(escape) + (lineOpt match { + case Some(line) => s"${list.head}, $line" :: list.tail + case None => list + }).distinct // expr.code, toCfgNode(expr).code could be the same + .mkString("
") } private def toCfgNode(node: StoredNode): CfgNode = {