Skip to content

Commit 602acc7

Browse files
committed
Add minor type inference fixes
1 parent c599ea7 commit 602acc7

26 files changed

+881
-561
lines changed

usvm-dataflow-ts/src/main/kotlin/org/usvm/dataflow/ts/infer/AccessPath.kt

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.jacodb.ets.base.EtsParameterRef
1010
import org.jacodb.ets.base.EtsStaticFieldRef
1111
import org.jacodb.ets.base.EtsThis
1212
import org.jacodb.ets.base.EtsValue
13+
import org.jacodb.ets.model.EtsClassSignature
1314

1415
data class AccessPath(val base: AccessPathBase, val accesses: List<Accessor>) {
1516
operator fun plus(accessor: Accessor) = AccessPath(base, accesses + accessor)
@@ -44,8 +45,8 @@ sealed interface AccessPathBase {
4445
override fun toString(): String = "<this>"
4546
}
4647

47-
object Static : AccessPathBase {
48-
override fun toString(): String = "<static>"
48+
data class Static(val clazz: EtsClassSignature) : AccessPathBase {
49+
override fun toString(): String = "static(${clazz.name})"
4950
}
5051

5152
data class Arg(val index: Int) : AccessPathBase {
@@ -54,6 +55,34 @@ sealed interface AccessPathBase {
5455

5556
data class Local(val name: String) : AccessPathBase {
5657
override fun toString(): String = "local($name)"
58+
59+
fun tryGetOrdering(): Int? {
60+
if (name.startsWith("%")) {
61+
val ix = name.substring(1).toIntOrNull()
62+
if (ix != null) {
63+
return ix
64+
}
65+
}
66+
if (name.startsWith("\$v")) {
67+
val ix = name.substring(2).toIntOrNull()
68+
if (ix != null) {
69+
return 10_000 + ix
70+
}
71+
}
72+
if (name.startsWith("\$temp")) {
73+
val ix = name.substring(5).toIntOrNull()
74+
if (ix != null) {
75+
return 20_000 + ix
76+
}
77+
}
78+
if (name.startsWith("_tmp")) {
79+
val ix = name.substring(4).toIntOrNull()
80+
if (ix != null) {
81+
return 30_000 + ix
82+
}
83+
}
84+
return null
85+
}
5786
}
5887

5988
data class Const(val constant: EtsConstant) : AccessPathBase {
@@ -86,7 +115,10 @@ fun EtsEntity.toPathOrNull(): AccessPath? = when (this) {
86115
it + FieldAccessor(field.name)
87116
}
88117

89-
is EtsStaticFieldRef -> AccessPath(AccessPathBase.Static, listOf(FieldAccessor(field.name)))
118+
is EtsStaticFieldRef -> {
119+
val base = AccessPathBase.Static(field.enclosingClass)
120+
AccessPath(base, listOf(FieldAccessor(field.name)))
121+
}
90122

91123
is EtsCastExpr -> arg.toPathOrNull()
92124

usvm-dataflow-ts/src/main/kotlin/org/usvm/dataflow/ts/infer/BackwardFlowFunctions.kt

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class BackwardFlowFunctions(
3434
val graph: ApplicationGraph<EtsMethod, EtsStmt>,
3535
val dominators: (EtsMethod) -> GraphDominators<EtsStmt>,
3636
val savedTypes: MutableMap<EtsType, MutableList<EtsTypeFact>>,
37+
val doAddKnownTypes: Boolean = true,
3738
) : FlowFunctions<BackwardTypeDomainFact, EtsMethod, EtsStmt> {
3839

3940
// private val aliasesCache: MutableMap<EtsMethod, Map<EtsStmt, Pair<AliasInfo, AliasInfo>>> = hashMapOf()
@@ -200,9 +201,21 @@ class BackwardFlowFunctions(
200201
// Case `return x`
201202
// ∅ |= x:unknown
202203
if (current is EtsReturnStmt) {
203-
val variable = current.returnValue?.toBase()
204-
if (variable != null) {
205-
result += TypedVariable(variable, EtsTypeFact.UnknownEtsTypeFact)
204+
val returnValue = current.returnValue
205+
if (returnValue != null) {
206+
val variable = returnValue.toBase()
207+
val type = if (doAddKnownTypes) {
208+
EtsTypeFact.from(returnValue.type).let {
209+
if (it is EtsTypeFact.AnyEtsTypeFact) {
210+
EtsTypeFact.UnknownEtsTypeFact
211+
} else {
212+
it
213+
}
214+
}
215+
} else {
216+
EtsTypeFact.UnknownEtsTypeFact
217+
}
218+
result += TypedVariable(variable, type)
206219
}
207220
}
208221

@@ -223,10 +236,23 @@ class BackwardFlowFunctions(
223236
if (rhv.accesses.isEmpty()) {
224237
// Case `x... := y`
225238
// ∅ |= y:unknown
226-
result += TypedVariable(y, EtsTypeFact.UnknownEtsTypeFact)
239+
val type = if (doAddKnownTypes) {
240+
EtsTypeFact.from(current.rhv.type).let { it ->
241+
if (it is EtsTypeFact.AnyEtsTypeFact) {
242+
EtsTypeFact.UnknownEtsTypeFact
243+
} else {
244+
it
245+
}
246+
}
247+
} else {
248+
EtsTypeFact.UnknownEtsTypeFact
249+
}
250+
result += TypedVariable(y, type)
227251
} else {
228252
// Case `x := y.f` OR `x := y[i]`
229253

254+
// TODO: handle known (real) type
255+
230256
check(rhv.accesses.size == 1)
231257
when (val accessor = rhv.accesses.single()) {
232258
// Case `x := y.f`
@@ -359,6 +385,11 @@ class BackwardFlowFunctions(
359385
cls = null,
360386
properties = mapOf(a.name to fact.type)
361387
)
388+
// val realType = EtsTypeFact.from(current.rhv.type)
389+
// val type = newType.intersect(realType) ?: run {
390+
// logger.warn { "Empty intersection of fact and real type: $newType & $realType" }
391+
// newType
392+
// }
362393
result += TypedVariable(y, type).withTypeGuards(current)
363394
// aliases: +|= z:{f:T}
364395
// for (z in preAliases.getAliases(AccessPath(y, emptyList()))) {
@@ -373,6 +404,11 @@ class BackwardFlowFunctions(
373404
// x:T |= x:T (keep) + y:Array<T>
374405
val y = rhv.base
375406
val type = EtsTypeFact.ArrayEtsTypeFact(elementType = fact.type)
407+
// val realType = EtsTypeFact.from(current.rhv.type)
408+
// val type = newType.intersect(realType) ?: run {
409+
// logger.warn { "Empty intersection of fact and real type: $newType & $realType" }
410+
// newType
411+
// }
376412
val newFact = TypedVariable(y, type).withTypeGuards(current)
377413
return listOf(fact, newFact)
378414
}
@@ -386,11 +422,11 @@ class BackwardFlowFunctions(
386422
// Case `x.f := y`
387423
is FieldAccessor -> {
388424
if (fact.type is EtsTypeFact.UnionEtsTypeFact) {
389-
TODO("Support union type for x.f := y in BW-sequent")
425+
// TODO("Support union type for x.f := y in BW-sequent")
390426
}
391427

392428
if (fact.type is EtsTypeFact.IntersectionEtsTypeFact) {
393-
TODO("Support intersection type for x.f := y in BW-sequent")
429+
// TODO("Support intersection type for x.f := y in BW-sequent")
394430
}
395431

396432
// x:primitive |= x:primitive (pass)
@@ -412,11 +448,11 @@ class BackwardFlowFunctions(
412448
// Case `x[i] := y`
413449
is ElementAccessor -> {
414450
if (fact.type is EtsTypeFact.UnionEtsTypeFact) {
415-
TODO("Support union type for x[i] := y in BW-sequent")
451+
// TODO("Support union type for x[i] := y in BW-sequent")
416452
}
417453

418454
if (fact.type is EtsTypeFact.IntersectionEtsTypeFact) {
419-
TODO("Support intersection type for x[i] := y in BW-sequent")
455+
// TODO("Support intersection type for x[i] := y in BW-sequent")
420456
}
421457

422458
// x:Array<T> |= x:Array<T> (pass)

usvm-dataflow-ts/src/main/kotlin/org/usvm/dataflow/ts/infer/EtsTypeFact.kt

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.jacodb.ets.base.EtsUndefinedType
1717
import org.jacodb.ets.base.EtsUnionType
1818
import org.jacodb.ets.base.EtsUnknownType
1919
import org.jacodb.ets.base.INSTANCE_INIT_METHOD_NAME
20+
import org.usvm.dataflow.ts.util.Globals
2021

2122
private val logger = KotlinLogging.logger {}
2223

@@ -44,7 +45,9 @@ sealed interface EtsTypeFact {
4445
}
4546
}
4647

47-
fun intersect(other: EtsTypeFact): EtsTypeFact? {
48+
fun intersect(other: EtsTypeFact?): EtsTypeFact? {
49+
if (other == null) return this
50+
4851
if (this == other) return this
4952

5053
if (other is UnknownEtsTypeFact) return this
@@ -151,10 +154,41 @@ sealed interface EtsTypeFact {
151154
override fun toString(): String = "Array<$elementType>"
152155
}
153156

154-
data class ObjectEtsTypeFact(
157+
@ConsistentCopyVisibility
158+
data class ObjectEtsTypeFact private constructor(
155159
val cls: EtsType?,
156160
val properties: Map<String, EtsTypeFact>,
157161
) : BasicType {
162+
companion object {
163+
operator fun invoke(
164+
cls: EtsType?,
165+
properties: Map<String, EtsTypeFact>,
166+
): ObjectEtsTypeFact {
167+
if (cls is EtsUnclearRefType && cls.name == "Object") {
168+
return ObjectEtsTypeFact(null, properties)
169+
}
170+
return ObjectEtsTypeFact(cls, properties)
171+
}
172+
}
173+
174+
fun getRealProperties(): Map<String, EtsTypeFact> {
175+
val scene = Globals.scene ?: return properties
176+
if (cls == null || cls !is EtsClassType) {
177+
return properties
178+
}
179+
val clazz = scene.projectAndSdkClasses.firstOrNull { it.signature == cls.signature }
180+
?: return properties
181+
val props = properties.toMutableMap()
182+
clazz.methods.forEach { m ->
183+
props.merge(m.name, FunctionEtsTypeFact) { old, new ->
184+
old.intersect(new).also {
185+
if (it == null) logger.warn { "Empty intersection: $old & $new" }
186+
}
187+
}
188+
}
189+
return props
190+
}
191+
158192
override fun toString(): String {
159193
val clsName = cls?.typeName?.takeUnless { it.startsWith(ANONYMOUS_CLASS_PREFIX) } ?: "Object"
160194
val funProps = properties.entries
@@ -360,23 +394,26 @@ sealed interface EtsTypeFact {
360394
return mkIntersectionType(guardedType, other)
361395
}
362396

397+
private fun tryIntersect(cls1: EtsType?, cls2: EtsType?): EtsType? {
398+
if (cls1 == cls2) return cls1
399+
if (cls1 == null) return cls2
400+
if (cls2 == null) return cls1
401+
// TODO: isSubtype
402+
return null
403+
}
404+
363405
private fun intersect(obj1: ObjectEtsTypeFact, obj2: ObjectEtsTypeFact): EtsTypeFact? {
364-
val intersectionProperties = obj1.properties.toMutableMap()
365-
for ((property, type) in obj2.properties) {
406+
val intersectionProperties = obj1.getRealProperties().toMutableMap()
407+
for ((property, type) in obj2.getRealProperties()) {
366408
val currentType = intersectionProperties[property]
367409
if (currentType == null) {
368410
intersectionProperties[property] = type
369-
continue
411+
} else {
412+
intersectionProperties[property] = currentType.intersect(type)
413+
?: return null
370414
}
371-
372-
intersectionProperties[property] = currentType.intersect(type) ?: return null
373-
}
374-
375-
val intersectionCls = if (obj1.cls != null && obj2.cls != null) {
376-
obj1.cls.takeIf { it == obj2.cls }
377-
} else {
378-
obj1.cls ?: obj2.cls
379415
}
416+
val intersectionCls = tryIntersect(obj1.cls, obj2.cls)
380417
return ObjectEtsTypeFact(intersectionCls, intersectionProperties)
381418
}
382419

@@ -391,7 +428,7 @@ sealed interface EtsTypeFact {
391428
type
392429
}
393430

394-
return ObjectEtsTypeFact(obj.cls, intersectionProperties)
431+
return ObjectEtsTypeFact(null, intersectionProperties)
395432
}
396433

397434
private fun union(unionType: UnionEtsTypeFact, other: EtsTypeFact): EtsTypeFact {
@@ -504,7 +541,7 @@ sealed interface EtsTypeFact {
504541
is EtsUnclearRefType -> ObjectEtsTypeFact(type, emptyMap())
505542
// is EtsGenericType -> TODO()
506543
else -> {
507-
logger.error { "Unsupported type: $type" }
544+
logger.warn { "Unsupported type: $type" }
508545
UnknownEtsTypeFact
509546
}
510547
}
Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.usvm.dataflow.ts.infer
22

3+
import org.jacodb.ets.base.EtsNopStmt
34
import org.jacodb.ets.base.EtsStmt
45
import org.jacodb.ets.base.EtsType
56
import org.jacodb.ets.model.EtsMethod
@@ -10,7 +11,7 @@ import org.usvm.dataflow.ts.graph.EtsApplicationGraph
1011

1112
class ForwardAnalyzer(
1213
val graph: EtsApplicationGraph,
13-
methodInitialTypes: Map<EtsMethod, EtsMethodTypeFacts>,
14+
methodInitialTypes: Map<EtsMethod, Map<AccessPathBase, EtsTypeFact>>,
1415
typeInfo: Map<EtsType, EtsTypeFact>,
1516
doAddKnownTypes: Boolean = true,
1617
) : Analyzer<ForwardTypeDomainFact, AnalyzerEvent, EtsMethod, EtsStmt> {
@@ -27,18 +28,18 @@ class ForwardAnalyzer(
2728
override fun handleNewEdge(edge: Edge<ForwardTypeDomainFact, EtsStmt>): List<AnalyzerEvent> {
2829
val (startVertex, currentVertex) = edge
2930
val (current, currentFact) = currentVertex
30-
3131
val method = graph.methodOf(current)
32-
val currentIsExit = current in graph.exitPoints(method)
33-
34-
if (!currentIsExit) return emptyList()
35-
36-
return listOf(
37-
ForwardSummaryAnalyzerEvent(
38-
method = method,
39-
initialVertex = startVertex,
40-
exitVertex = currentVertex,
32+
val currentIsExit = current in graph.exitPoints(method) ||
33+
(current is EtsNopStmt && graph.successors(current).none())
34+
if (currentIsExit) {
35+
return listOf(
36+
ForwardSummaryAnalyzerEvent(
37+
method = method,
38+
initialVertex = startVertex,
39+
exitVertex = currentVertex,
40+
)
4141
)
42-
)
42+
}
43+
return emptyList()
4344
}
4445
}

0 commit comments

Comments
 (0)