From 8c4e9d3914323caca63162e91f5509b29f386014 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 20 Aug 2025 18:59:02 +0300 Subject: [PATCH] Add FieldRef and ArrayAccess in CFG DSL --- .../kotlin/org/jacodb/ets/dsl/BlockStmt.kt | 2 +- .../src/main/kotlin/org/jacodb/ets/dsl/DSL.kt | 15 ++++ .../main/kotlin/org/jacodb/ets/dsl/Expr.kt | 29 ++++++- .../main/kotlin/org/jacodb/ets/dsl/Node.kt | 2 +- .../org/jacodb/ets/dsl/ProgramBuilder.kt | 15 +++- .../main/kotlin/org/jacodb/ets/dsl/Stmt.kt | 2 +- .../main/kotlin/org/jacodb/ets/dsl/ToDot.kt | 50 +++++++----- .../kotlin/org/jacodb/ets/model/Modifiers.kt | 6 +- .../org/jacodb/ets/utils/BlockCfgBuilder.kt | 78 ++++++++++++++++--- 9 files changed, 155 insertions(+), 44 deletions(-) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockStmt.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockStmt.kt index bdabe9714..b8b265c08 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockStmt.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockStmt.kt @@ -24,7 +24,7 @@ sealed interface BlockStmt data object BlockNop : BlockStmt data class BlockAssign( - val target: Local, + val target: LValue, val expr: Expr, ) : BlockStmt diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt index 9de90491c..5ab2256ec 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt @@ -62,6 +62,21 @@ private fun main() { } // x.foo := 35 + assign(local("x").field("foo"), const(35)) + + // arr[i] := 42 + assign(local("arr")[local("i")], const(42)) + + // MyClass.staticField := "hello" + assign(staticFieldRef("MyClass", "staticField"), const("hello")) + + // obj.field1.field2 := 100 + assign(local("obj").field("field1").field("field2"), const(100)) + + // objects[0].value := 99 + assign(local("objects")[const(0)].field("value"), const(99)) + + // x.foo := 35 (using CustomStmt approach) customStmt { loc -> EtsAssignStmt( location = loc, diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt index 8e697be03..b05ba25d6 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt @@ -18,7 +18,7 @@ package org.jacodb.ets.dsl sealed interface Expr -data class Local(val name: String) : Expr { +data class Local(val name: String) : LValue { override fun toString() = name } @@ -30,6 +30,10 @@ object ThisRef : Expr { override fun toString() = "this" } +data class ConstantInt(val value: Int) : Expr { + override fun toString() = "const($value)" +} + data class ConstantNumber(val value: Double) : Expr { override fun toString() = "const($value)" } @@ -79,3 +83,26 @@ data class UnaryExpr( ) : Expr { override fun toString() = "${operator.name.lowercase()}($expr)" } + +sealed interface LValue : Expr + +data class FieldRef( + val instance: Expr, + val fieldName: String, +) : LValue { + override fun toString() = "$instance.$fieldName" +} + +data class StaticFieldRef( + val className: String, + val fieldName: String, +) : LValue { + override fun toString() = "$className.$fieldName" +} + +data class ArrayAccess( + val array: Expr, + val index: Expr, +) : LValue { + override fun toString() = "$array[$index]" +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Node.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Node.kt index ccc2160a9..3178b9588 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Node.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Node.kt @@ -24,7 +24,7 @@ sealed interface Node data object Nop : Node data class Assign( - val target: Local, + val target: LValue, val expr: Expr, ) : Node diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ProgramBuilder.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ProgramBuilder.kt index 634f1ee51..5f2c64c3d 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ProgramBuilder.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ProgramBuilder.kt @@ -24,7 +24,7 @@ import org.jacodb.ets.model.EtsStmtLocation interface ProgramBuilder { fun nop() - fun assign(target: Local, expr: Expr) + fun assign(target: LValue, expr: Expr) fun ret(expr: Expr) fun label(name: String) fun goto(label: String) @@ -36,11 +36,18 @@ interface ProgramBuilder { fun ProgramBuilder.local(name: String) = Local(name) fun ProgramBuilder.param(index: Int) = Parameter(index) fun ProgramBuilder.thisRef() = ThisRef -fun ProgramBuilder.const(value: Int) = ConstantNumber(value.toDouble()) +fun ProgramBuilder.const(value: Int) = ConstantInt(value) fun ProgramBuilder.const(value: Double) = ConstantNumber(value) fun ProgramBuilder.const(value: Boolean) = ConstantBoolean(value) fun ProgramBuilder.const(value: String) = ConstantString(value) +fun ProgramBuilder.fieldRef(instance: Expr, fieldName: String) = FieldRef(instance, fieldName) +fun ProgramBuilder.staticFieldRef(className: String, fieldName: String) = StaticFieldRef(className, fieldName) +fun ProgramBuilder.arrayAccess(array: Expr, index: Expr) = ArrayAccess(array, index) + +fun Expr.field(name: String) = FieldRef(this, name) +operator fun Expr.get(index: Expr) = ArrayAccess(this, index) + fun ProgramBuilder.and(left: Expr, right: Expr) = BinaryExpr(BinaryOperator.AND, left, right) fun ProgramBuilder.or(left: Expr, right: Expr) = BinaryExpr(BinaryOperator.OR, left, right) fun ProgramBuilder.eq(left: Expr, right: Expr) = BinaryExpr(BinaryOperator.EQ, left, right) @@ -89,7 +96,7 @@ class ProgramBuilderImpl : ProgramBuilder { _nodes += Nop } - override fun assign(target: Local, expr: Expr) { + override fun assign(target: LValue, expr: Expr) { _nodes += Assign(target, expr) } @@ -140,7 +147,7 @@ class IfBuilder : ProgramBuilder { thenBuilder.ifStmt(condition, block) override fun nop() = thenBuilder.nop() - override fun assign(target: Local, expr: Expr) = thenBuilder.assign(target, expr) + override fun assign(target: LValue, expr: Expr) = thenBuilder.assign(target, expr) override fun ret(expr: Expr) = thenBuilder.ret(expr) override fun label(name: String) = thenBuilder.label(name) override fun goto(label: String) = thenBuilder.goto(label) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Stmt.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Stmt.kt index 3b7658763..9d9df5557 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Stmt.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Stmt.kt @@ -31,7 +31,7 @@ data class NopStmt( data class AssignStmt( override val location: StmtLocation, - val target: Local, + val target: LValue, val expr: Expr, ) : Stmt diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ToDot.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ToDot.kt index ff0b16ca6..081dab0f0 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ToDot.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ToDot.kt @@ -16,17 +16,19 @@ package org.jacodb.ets.dsl -import org.jacodb.ets.utils.toDotLabel import java.util.IdentityHashMap -private fun Node.toDotLabel() = when (this) { - is Nop -> "nop" - is Assign -> "$target := $expr" - is Return -> "return $expr" - is If -> "if ($condition)" - is Label -> "label $name" - is Goto -> "goto $targetLabel" - is CustomEts -> "???" +private fun Node.toDotLabel(): String { + val label = when (this) { + is Nop -> "nop" + is Assign -> "$target := $expr" + is Return -> "return $expr" + is If -> "if ($condition)" + is Label -> "label $name" + is Goto -> "goto $targetLabel" + is CustomEts -> "???" + } + return label.replace("\"", "\\\"") } fun Program.toDot(): String { @@ -99,12 +101,15 @@ fun Program.toDot(): String { return lines.joinToString("\n") } -private fun BlockStmt.toDotLabel() = when (this) { - is BlockAssign -> "$target := $expr" - is BlockReturn -> "return $expr" - is BlockIf -> "if ($condition)" - is BlockNop -> "nop" - is BlockCustomEts -> "???" +private fun BlockStmt.toDotLabel(): String { + val label = when (this) { + is BlockAssign -> "$target := $expr" + is BlockReturn -> "return $expr" + is BlockIf -> "if ($condition)" + is BlockNop -> "nop" + is BlockCustomEts -> "???" + } + return label.replace("\"", "\\\"") } fun BlockCfg.toDot(): String { @@ -136,12 +141,15 @@ fun BlockCfg.toDot(): String { return lines.joinToString("\n") } -private fun Stmt.toDotLabel() = when (this) { - is NopStmt -> "nop" - is AssignStmt -> "$target := $expr" - is ReturnStmt -> "return $expr" - is IfStmt -> "if ($condition)" - is CustomEtsStmt -> "???" +private fun Stmt.toDotLabel(): String { + val label = when (this) { + is NopStmt -> "nop" + is AssignStmt -> "$target := $expr" + is ReturnStmt -> "return $expr" + is IfStmt -> "if ($condition)" + is CustomEtsStmt -> "???" + } + return label.replace("\"", "\\\"") } fun LinearizedCfg.toDot(): String { diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Modifiers.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Modifiers.kt index d9ae787ec..033e6e4f9 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Modifiers.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Modifiers.kt @@ -52,6 +52,9 @@ interface WithModifiers { val isDeclare: Boolean get() = hasModifier(EtsModifier.DECLARE) fun hasModifier(modifier: EtsModifier): Boolean + + val modifiersList: List + get() = EtsModifier.entries.filter { hasModifier(it) } } @JvmInline @@ -64,8 +67,5 @@ value class EtsModifiers(val mask: Int) : WithModifiers { } } - val modifiers: List - get() = EtsModifier.entries.filter { hasModifier(it) } - override fun hasModifier(modifier: EtsModifier): Boolean = (mask and modifier.value) != 0 } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt index 7d4df727e..3c89471ad 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt @@ -16,6 +16,7 @@ package org.jacodb.ets.utils +import org.jacodb.ets.dsl.ArrayAccess import org.jacodb.ets.dsl.BinaryExpr import org.jacodb.ets.dsl.BinaryOperator import org.jacodb.ets.dsl.Block @@ -26,30 +27,38 @@ import org.jacodb.ets.dsl.BlockIf import org.jacodb.ets.dsl.BlockNop import org.jacodb.ets.dsl.BlockReturn import org.jacodb.ets.dsl.ConstantBoolean +import org.jacodb.ets.dsl.ConstantInt import org.jacodb.ets.dsl.ConstantNumber import org.jacodb.ets.dsl.ConstantString import org.jacodb.ets.dsl.CustomBinaryExpr import org.jacodb.ets.dsl.CustomUnaryExpr import org.jacodb.ets.dsl.CustomValue import org.jacodb.ets.dsl.Expr +import org.jacodb.ets.dsl.FieldRef import org.jacodb.ets.dsl.Local import org.jacodb.ets.dsl.Parameter +import org.jacodb.ets.dsl.StaticFieldRef import org.jacodb.ets.dsl.ThisRef import org.jacodb.ets.dsl.UnaryExpr import org.jacodb.ets.dsl.UnaryOperator import org.jacodb.ets.model.BasicBlock import org.jacodb.ets.model.EtsAddExpr import org.jacodb.ets.model.EtsAndExpr +import org.jacodb.ets.model.EtsArrayAccess import org.jacodb.ets.model.EtsAssignStmt import org.jacodb.ets.model.EtsBlockCfg import org.jacodb.ets.model.EtsBooleanConstant +import org.jacodb.ets.model.EtsClassSignature import org.jacodb.ets.model.EtsDivExpr import org.jacodb.ets.model.EtsEntity import org.jacodb.ets.model.EtsEqExpr +import org.jacodb.ets.model.EtsFieldSignature import org.jacodb.ets.model.EtsGtEqExpr import org.jacodb.ets.model.EtsGtExpr import org.jacodb.ets.model.EtsIfStmt import org.jacodb.ets.model.EtsImmediate +import org.jacodb.ets.model.EtsInstanceFieldRef +import org.jacodb.ets.model.EtsLValue import org.jacodb.ets.model.EtsLocal import org.jacodb.ets.model.EtsLtEqExpr import org.jacodb.ets.model.EtsLtExpr @@ -64,6 +73,7 @@ import org.jacodb.ets.model.EtsOrExpr import org.jacodb.ets.model.EtsParameterRef import org.jacodb.ets.model.EtsRemExpr import org.jacodb.ets.model.EtsReturnStmt +import org.jacodb.ets.model.EtsStaticFieldRef import org.jacodb.ets.model.EtsStmt import org.jacodb.ets.model.EtsStmtLocation import org.jacodb.ets.model.EtsStrictEqExpr @@ -124,23 +134,27 @@ class EtsBlockCfgBuilder( is Local -> { EtsLocal( name = name, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) } is Parameter -> { EtsParameterRef( index = index, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) } ThisRef -> { EtsThis( - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) } + is ConstantInt -> { + EtsNumberConstant(value = value.toDouble()) + } + is ConstantNumber -> { EtsNumberConstant(value = value) } @@ -153,6 +167,43 @@ class EtsBlockCfgBuilder( EtsStringConstant(value = value) } + is FieldRef -> { + val instanceEntity = instance.toEtsEntity() + val instanceLocal = ensureLocal(instanceEntity) + EtsInstanceFieldRef( + instance = instanceLocal, + field = EtsFieldSignature( + enclosingClass = EtsClassSignature.UNKNOWN, + name = fieldName, + type = EtsUnknownType, + ), + type = EtsUnknownType + ) + } + + is StaticFieldRef -> { + EtsStaticFieldRef( + field = EtsFieldSignature( + enclosingClass = EtsClassSignature.UNKNOWN, + name = fieldName, + type = EtsUnknownType, + ), + type = EtsUnknownType + ) + } + + is ArrayAccess -> { + val arrayEntity = array.toEtsEntity() + val arrayLocal = ensureLocal(arrayEntity) + val indexEntity = index.toEtsEntity() + val indexValue = ensureImmediate(indexEntity) + EtsArrayAccess( + array = arrayLocal, + index = indexValue, + type = EtsUnknownType, + ) + } + is UnaryExpr -> { val arg = ensureImmediate(expr.toEtsEntity()) when (operator) { @@ -162,7 +213,7 @@ class EtsBlockCfgBuilder( UnaryOperator.NEG -> EtsNegExpr( arg = arg, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) } } @@ -174,13 +225,13 @@ class EtsBlockCfgBuilder( BinaryOperator.AND -> EtsAndExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) BinaryOperator.OR -> EtsOrExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) BinaryOperator.EQ -> EtsEqExpr( @@ -226,31 +277,31 @@ class EtsBlockCfgBuilder( BinaryOperator.ADD -> EtsAddExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) BinaryOperator.SUB -> EtsSubExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) BinaryOperator.MUL -> EtsMulExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) BinaryOperator.DIV -> EtsDivExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) BinaryOperator.REM -> EtsRemExpr( left = left, right = right, - type = EtsUnknownType, // TODO + type = EtsUnknownType, ) } } @@ -278,8 +329,11 @@ class EtsBlockCfgBuilder( } is BlockAssign -> { - val lhv = stmt.target.toEtsEntity() as EtsLocal // safe cast + val lhv = stmt.target.toEtsEntity() val rhv = stmt.expr.toEtsEntity() + check(lhv is EtsLValue) { + "Assignment target must be an LValue, got: ${lhv::class.simpleName}" + } etsStatements += EtsAssignStmt( location = stub, lhv = lhv,