Skip to content

Improve dot ast #2002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 &gt; 42),if (y &gt; 42))<SUB>5</SUB>> ]""") and
include("""[label = <CONTROL_STRUCTURE, 5<BR/>if (y &gt; 42)> ]""") and
endWith("}\n")
)
}
Expand All @@ -52,18 +52,16 @@ 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 &gt; 42") and include("IDENTIFIER,y") and not include "x * 2")
x should (include("y &gt; 42") and include("IDENTIFIER, 5<BR/>y") and not include "x * 2")
}
}

"allow plotting sub trees of methods correctly escaped" in {
inside(cpg.method.name("lemon").dotAst.l) { case List(x) =>
x should (
startWith("digraph \"lemon\"") and
include("""[label = <(goog,goog(&quot;\&quot;yes\&quot;&quot;))<SUB>18</SUB>> ]""") and
include(
"""[label = <(LITERAL,&quot;\&quot;yes\&quot;&quot;,goog(&quot;\&quot;yes\&quot;&quot;))<SUB>18</SUB>> ]"""
) and
include("""[label = <CALL, 18<BR/>goog<BR/>goog(&quot;\&quot;yes\&quot;&quot;)> ]""") and
include("""[label = <LITERAL, 18<BR/>&quot;\&quot;yes\&quot;&quot;> ]""") and
endWith("}\n")
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <(&lt;operator&gt;.greaterThan,x &gt; 8)<SUB>3</SUB>> ]""") and
include("""[label = <(&lt;operator&gt;.assignment,z = a(x))<SUB>4</SUB>> ]""") and
include("""[label = <(a,a(x))<SUB>4</SUB>> ]""") and
include("""[label = <CALL, 3<BR/>&lt;operator&gt;.greaterThan<BR/>x &gt; 8> ]""") and
include("""[label = <CALL, 4<BR/>&lt;operator&gt;.assignment<BR/>z = a(x)> ]""") and
include("""[label = <CALL, 4<BR/>a<BR/>a(x)> ]""") and
endWith("}\n")
)
val lines = x.split("\n")
Expand All @@ -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 = <(&lt;operator&gt;.greaterThan,x &gt; 8)<SUB>3</SUB>> ]""") and
include("""[label = <(&lt;operator&gt;.assignment,z = a(x))<SUB>4</SUB>> ]""") and
include("""[label = <(a,a(x))<SUB>4</SUB>> ]""") and
include("""[label = <CALL, 3<BR/>&lt;operator&gt;.greaterThan<BR/>x &gt; 8> ]""") and
include("""[label = <CALL, 4<BR/>&lt;operator&gt;.assignment<BR/>z = a(x)> ]""") and
include("""[label = <CALL, 4<BR/>a<BR/>a(x)> ]""") and
endWith("}\n")
)
val lines = x.split("\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("(&lt;operator&gt;.assignment,i = 0)") and
include("&lt;operator&gt;.assignment<BR/>i = 0") and
endWith("}\n")
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) =>
Expand All @@ -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"<SUB>${maybeLineNo.get()}</SUB>" 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("<BR/>")
}

private def toCfgNode(node: StoredNode): CfgNode = {
Expand Down