Skip to content

Commit 2f69601

Browse files
committed
Add CfgDto.toDot
1 parent dfb9993 commit 2f69601

2 files changed

Lines changed: 275 additions & 0 deletions

File tree

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.ets.utils
18+
19+
import org.jacodb.ets.dto.AliasTypeDto
20+
import org.jacodb.ets.dto.AnyTypeDto
21+
import org.jacodb.ets.dto.ArrayRefDto
22+
import org.jacodb.ets.dto.ArrayTypeDto
23+
import org.jacodb.ets.dto.AssignStmtDto
24+
import org.jacodb.ets.dto.AwaitExprDto
25+
import org.jacodb.ets.dto.BinaryOperationDto
26+
import org.jacodb.ets.dto.BooleanTypeDto
27+
import org.jacodb.ets.dto.CallStmtDto
28+
import org.jacodb.ets.dto.CastExprDto
29+
import org.jacodb.ets.dto.CaughtExceptionRefDto
30+
import org.jacodb.ets.dto.CfgDto
31+
import org.jacodb.ets.dto.ClassTypeDto
32+
import org.jacodb.ets.dto.ClosureFieldRefDto
33+
import org.jacodb.ets.dto.ConstantDto
34+
import org.jacodb.ets.dto.DeleteExprDto
35+
import org.jacodb.ets.dto.EnumValueTypeDto
36+
import org.jacodb.ets.dto.FunctionTypeDto
37+
import org.jacodb.ets.dto.GenericTypeDto
38+
import org.jacodb.ets.dto.GlobalRefDto
39+
import org.jacodb.ets.dto.IfStmtDto
40+
import org.jacodb.ets.dto.InstanceCallExprDto
41+
import org.jacodb.ets.dto.InstanceFieldRefDto
42+
import org.jacodb.ets.dto.InstanceOfExprDto
43+
import org.jacodb.ets.dto.IntersectionTypeDto
44+
import org.jacodb.ets.dto.LexicalEnvTypeDto
45+
import org.jacodb.ets.dto.LiteralTypeDto
46+
import org.jacodb.ets.dto.LocalDto
47+
import org.jacodb.ets.dto.NeverTypeDto
48+
import org.jacodb.ets.dto.NewArrayExprDto
49+
import org.jacodb.ets.dto.NewExprDto
50+
import org.jacodb.ets.dto.NopStmtDto
51+
import org.jacodb.ets.dto.NullTypeDto
52+
import org.jacodb.ets.dto.NumberTypeDto
53+
import org.jacodb.ets.dto.ParameterRefDto
54+
import org.jacodb.ets.dto.PrimitiveLiteralDto
55+
import org.jacodb.ets.dto.PtrCallExprDto
56+
import org.jacodb.ets.dto.RawStmtDto
57+
import org.jacodb.ets.dto.RawTypeDto
58+
import org.jacodb.ets.dto.RawValueDto
59+
import org.jacodb.ets.dto.RelationOperationDto
60+
import org.jacodb.ets.dto.ReturnStmtDto
61+
import org.jacodb.ets.dto.ReturnVoidStmtDto
62+
import org.jacodb.ets.dto.StaticCallExprDto
63+
import org.jacodb.ets.dto.StaticFieldRefDto
64+
import org.jacodb.ets.dto.StmtDto
65+
import org.jacodb.ets.dto.StringTypeDto
66+
import org.jacodb.ets.dto.ThisRefDto
67+
import org.jacodb.ets.dto.ThrowStmtDto
68+
import org.jacodb.ets.dto.TupleTypeDto
69+
import org.jacodb.ets.dto.TypeDto
70+
import org.jacodb.ets.dto.TypeOfExprDto
71+
import org.jacodb.ets.dto.UnaryOperationDto
72+
import org.jacodb.ets.dto.UnclearReferenceTypeDto
73+
import org.jacodb.ets.dto.UndefinedTypeDto
74+
import org.jacodb.ets.dto.UnionTypeDto
75+
import org.jacodb.ets.dto.UnknownTypeDto
76+
import org.jacodb.ets.dto.ValueDto
77+
import org.jacodb.ets.dto.VoidTypeDto
78+
import org.jacodb.ets.dto.YieldExprDto
79+
80+
/**
81+
* Convert a CFG DTO to DOT format for visualization with Graphviz.
82+
*
83+
* This is a secondary conversion function for DTO-based CFGs.
84+
* For the main ETS model, use [org.jacodb.ets.model.EtsBlockCfg.toDot] instead.
85+
*
86+
* @param useHtml if true, uses HTML table format for blocks; if false, uses plain text
87+
* @return DOT graph string
88+
*/
89+
fun CfgDto.toDot(
90+
useHtml: Boolean = true,
91+
): String {
92+
val lines = mutableListOf<String>()
93+
lines += "digraph cfg {"
94+
lines += " node [shape=${if (useHtml) "none" else "rect"} fontname=\"monospace\"]"
95+
96+
// Nodes
97+
for (block in blocks) {
98+
if (useHtml) {
99+
val s = block.stmts.joinToString("") {
100+
it.toDotLabel().htmlEncode() + "<br/>"
101+
}
102+
val h = "<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" +
103+
"<tr><td>" + "<b>Block #${block.id}</b>" + "</td></tr>" +
104+
"<tr><td balign=\"left\">" + s + "</td></tr>" +
105+
"</table>"
106+
lines += " ${block.id} [label=<${h}>]"
107+
} else {
108+
val s = block.stmts.joinToString("") { it.toDotLabel() + "\\l" }
109+
lines += " ${block.id} [label=\"Block #${block.id}\\n$s\"]"
110+
}
111+
}
112+
113+
// Edges
114+
for (block in blocks) {
115+
val succs = block.successors
116+
if (succs.isEmpty()) continue
117+
if (succs.size == 1) {
118+
lines += " ${block.id} -> ${succs.single()}"
119+
} else {
120+
check(succs.size == 2) { "Block ${block.id} has ${succs.size} successors, expected 1 or 2" }
121+
val (trueBranch, falseBranch) = succs
122+
lines += " ${block.id} -> $trueBranch [label=\"true\"]"
123+
lines += " ${block.id} -> $falseBranch [label=\"false\"]"
124+
}
125+
}
126+
127+
lines += "}"
128+
return lines.joinToString("\n")
129+
}
130+
131+
/**
132+
* Convert a statement DTO to a DOT label string.
133+
*
134+
* This is a simple conversion that uses custom formatting for values.
135+
* For better formatting, consider converting to ETS model first and using
136+
* [org.jacodb.ets.model.EtsStmt.toDotLabel] instead.
137+
*/
138+
private fun StmtDto.toDotLabel(): String {
139+
val label = when (this) {
140+
is NopStmtDto -> "nop"
141+
is AssignStmtDto -> "${left.toDotLabel()} := ${right.toDotLabel()}"
142+
is ReturnVoidStmtDto -> "return"
143+
is ReturnStmtDto -> "return ${arg.toDotLabel()}"
144+
is ThrowStmtDto -> "throw ${arg.toDotLabel()}"
145+
is IfStmtDto -> "if (${condition.toDotLabel()})"
146+
is CallStmtDto -> "call ${expr.toDotLabel()}"
147+
is RawStmtDto -> "raw $kind"
148+
}
149+
return label.replace("\"", "\\\"")
150+
}
151+
152+
/**
153+
* Convert a value DTO to a DOT label string.
154+
*
155+
* Provides concise formatting for various value types without verbose data class toString().
156+
*/
157+
private fun ValueDto.toDotLabel(): String {
158+
return when (this) {
159+
// Immediates
160+
is LocalDto -> name
161+
is ConstantDto -> when (type) {
162+
is StringTypeDto -> "\"$value\""
163+
else -> value
164+
}
165+
166+
// References
167+
is ThisRefDto -> "this"
168+
is ParameterRefDto -> "arg$index"
169+
is CaughtExceptionRefDto -> "@caught ${type.toDotLabel()}"
170+
is GlobalRefDto -> name
171+
is ClosureFieldRefDto -> "${base.name}.$fieldName"
172+
is ArrayRefDto -> "${array.toDotLabel()}[${index.toDotLabel()}]"
173+
is InstanceFieldRefDto -> "${instance.toDotLabel()}.${field.name}"
174+
is StaticFieldRefDto -> "${field.declaringClass.name}.${field.name}"
175+
176+
// Expressions
177+
is NewExprDto -> "new ${classType.toDotLabel()}"
178+
is NewArrayExprDto -> "new Array<${elementType.toDotLabel()}>(${size.toDotLabel()})"
179+
is DeleteExprDto -> "delete ${arg.toDotLabel()}"
180+
is AwaitExprDto -> "await ${arg.toDotLabel()}"
181+
is YieldExprDto -> "yield ${arg.toDotLabel()}"
182+
is TypeOfExprDto -> "typeof ${arg.toDotLabel()}"
183+
is InstanceOfExprDto -> "${arg.toDotLabel()} instanceof ${checkType.toDotLabel()}"
184+
is CastExprDto -> "${arg.toDotLabel()} as ${type.toDotLabel()}"
185+
186+
// Unary operations
187+
is UnaryOperationDto -> "$op ${arg.toDotLabel()}"
188+
189+
// Binary operations
190+
is BinaryOperationDto -> "${left.toDotLabel()} $op ${right.toDotLabel()}"
191+
is RelationOperationDto -> "${left.toDotLabel()} $op ${right.toDotLabel()}"
192+
193+
// Call expressions
194+
is InstanceCallExprDto -> {
195+
val argsStr = args.joinToString(", ") { it.toDotLabel() }
196+
"call ${instance.toDotLabel()}.${method.name}($argsStr)"
197+
}
198+
199+
is StaticCallExprDto -> {
200+
val argsStr = args.joinToString(", ") { it.toDotLabel() }
201+
"static ${method.declaringClass.name}.${method.name}($argsStr)"
202+
}
203+
204+
is PtrCallExprDto -> {
205+
val argsStr = args.joinToString(", ") { it.toDotLabel() }
206+
"ptr ${ptr.toDotLabel()}.${method.name}($argsStr)"
207+
}
208+
209+
// Raw value
210+
is RawValueDto -> "raw:$kind"
211+
}
212+
}
213+
214+
/**
215+
* Convert a type DTO to a concise DOT label string.
216+
*/
217+
private fun TypeDto.toDotLabel(): String {
218+
return when (this) {
219+
// Primitive types
220+
is AnyTypeDto -> "any"
221+
is UnknownTypeDto -> "unknown"
222+
is UndefinedTypeDto -> "undefined"
223+
is NullTypeDto -> "null"
224+
is VoidTypeDto -> "void"
225+
is NeverTypeDto -> "never"
226+
is NumberTypeDto -> "number"
227+
is StringTypeDto -> "string"
228+
is BooleanTypeDto -> "boolean"
229+
230+
// Complex types
231+
is ClassTypeDto -> signature.name
232+
is ArrayTypeDto -> "${elementType.toDotLabel()}${"[]".repeat(dimensions)}"
233+
is TupleTypeDto -> types.joinToString(", ", "[", "]") { it.toDotLabel() }
234+
235+
is UnionTypeDto -> types.joinToString(" | ") {
236+
val s = it.toDotLabel()
237+
if (it is UnionTypeDto || it is IntersectionTypeDto) "($s)" else s
238+
}
239+
240+
is IntersectionTypeDto -> types.joinToString(" & ") {
241+
val s = it.toDotLabel()
242+
if (it is UnionTypeDto || it is IntersectionTypeDto) "($s)" else s
243+
}
244+
245+
is FunctionTypeDto -> {
246+
val params = signature.parameters.joinToString(", ") { it.type.toDotLabel() }
247+
"($params) => ${signature.returnType.toDotLabel()}"
248+
}
249+
250+
// Special types
251+
is GenericTypeDto -> name
252+
is AliasTypeDto -> name
253+
is EnumValueTypeDto -> "${signature.name}${name?.let { ".$it" } ?: ""}"
254+
is LexicalEnvTypeDto -> "LexicalEnv<${method.name}>"
255+
256+
is LiteralTypeDto -> when (literal) {
257+
is PrimitiveLiteralDto.StringLiteral -> "\"${literal.value}\""
258+
is PrimitiveLiteralDto.NumberLiteral -> literal.value.toString()
259+
is PrimitiveLiteralDto.BooleanLiteral -> literal.value.toString()
260+
}
261+
262+
is UnclearReferenceTypeDto -> {
263+
if (typeParameters.isEmpty()) name
264+
else "$name<${typeParameters.joinToString(", ") { it.toDotLabel() }}>"
265+
}
266+
267+
// Raw type
268+
is RawTypeDto -> "raw:$kind"
269+
}
270+
}

jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/ViewDot.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.jacodb.ets.utils
1818

19+
import org.jacodb.ets.dto.CfgDto
1920
import org.jacodb.ets.dto.EtsFileDto
2021
import org.jacodb.ets.model.EtsBlockCfg
2122
import org.jacodb.ets.model.EtsFile
@@ -66,3 +67,7 @@ fun EtsBlockCfg.view() {
6667
fun EtsLinearCfg.view() {
6768
view(toDot())
6869
}
70+
71+
fun CfgDto.view() {
72+
view(toDot())
73+
}

0 commit comments

Comments
 (0)