Skip to content

Commit d65d897

Browse files
authored
append mocking resolution to missed fields and methods (#150)
1 parent b2a58f3 commit d65d897

File tree

20 files changed

+350
-20
lines changed

20 files changed

+350
-20
lines changed

jacodb-api/src/main/kotlin/org/jacodb/api/JcClasspath.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ interface JcClassExtFeature : JcClasspathFeature {
180180

181181
}
182182

183+
interface JcLookupExtFeature : JcClasspathFeature {
184+
fun lookup(clazz: JcClassOrInterface): JcLookup<JcField, JcMethod>
185+
fun lookup(type: JcClassType): JcLookup<JcTypedField, JcTypedMethod>
186+
}
187+
183188
@JvmDefaultWithoutCompatibility
184189
interface JcInstExtFeature : JcClasspathFeature {
185190

jacodb-approximations/src/main/kotlin/org/jacodb/approximation/VirtualInstances.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ package org.jacodb.approximation
1818

1919
import org.jacodb.api.JcAnnotation
2020
import org.jacodb.api.JcMethodExtFeature
21-
import org.jacodb.api.JcMethodExtFeature.JcFlowGraphResult
22-
import org.jacodb.api.JcMethodExtFeature.JcInstListResult
23-
import org.jacodb.api.JcMethodExtFeature.JcRawInstListResult
21+
import org.jacodb.api.JcMethodExtFeature.*
2422
import org.jacodb.api.TypeName
2523
import org.jacodb.api.cfg.JcGraph
2624
import org.jacodb.api.cfg.JcInst
@@ -57,7 +55,7 @@ class JcEnrichedVirtualMethod(
5755

5856
override fun asmNode(): MethodNode = asmNode
5957

60-
override fun flowGraph(): JcGraph =featuresChain.call<JcMethodExtFeature, JcFlowGraphResult> {
58+
override fun flowGraph(): JcGraph = featuresChain.call<JcMethodExtFeature, JcFlowGraphResult> {
6159
it.flowGraph(this)
6260
}!!.flowGraph
6361

jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcAbstractLookup.kt

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package org.jacodb.impl.bytecode
1818

19-
import org.jacodb.api.JcAccessible
20-
import org.jacodb.api.JcMethod
21-
import org.jacodb.api.JcTypedMethod
19+
import org.jacodb.api.*
2220
import org.jacodb.api.ext.hasAnnotation
2321
import org.jacodb.api.ext.packageName
2422

@@ -92,4 +90,60 @@ internal interface PolymorphicSignatureSupport {
9290
val index = map { it.method }.indexOf(name)
9391
return if (index >= 0) get(index) else null
9492
}
93+
}
94+
95+
96+
abstract class DelegatingLookup<Field : JcAccessible, Method : JcAccessible>(
97+
private val ext: List<JcLookupExtFeature>,
98+
private val delegate: JcLookup<Field, Method>
99+
) : JcLookup<Field, Method> {
100+
101+
abstract fun lookupOf(feature: JcLookupExtFeature): JcLookup<Field, Method>
102+
103+
override fun field(name: String): Field? {
104+
return delegateCall { field(name) }
105+
}
106+
107+
override fun field(name: String, typeName: TypeName?): Field? {
108+
return delegateCall { field(name, typeName) }
109+
}
110+
111+
override fun method(name: String, description: String): Method? {
112+
return delegateCall { method(name, description) }
113+
}
114+
115+
override fun staticMethod(name: String, description: String): Method? {
116+
return delegateCall { staticMethod(name, description) }
117+
}
118+
119+
override fun specialMethod(name: String, description: String): Method? {
120+
return delegateCall { specialMethod(name, description) }
121+
}
122+
123+
private inline fun <Result> delegateCall(call: JcLookup<Field, Method>.() -> Result?): Result? {
124+
val result = delegate.call()
125+
if (result == null) {
126+
ext.forEach { e ->
127+
lookupOf(e).call()?.let {
128+
return it
129+
}
130+
}
131+
}
132+
return result
133+
}
134+
135+
}
136+
137+
class ClassDelegatingLookup(
138+
private val clazz: JcClassOrInterface, ext: List<JcLookupExtFeature>,
139+
delegate: JcLookup<JcField, JcMethod>
140+
) : DelegatingLookup<JcField, JcMethod>(ext, delegate) {
141+
override fun lookupOf(feature: JcLookupExtFeature): JcLookup<JcField, JcMethod> = feature.lookup(clazz)
142+
}
143+
144+
class TypeDelegatingLookup(
145+
private val type: JcClassType, ext: List<JcLookupExtFeature>,
146+
delegate: JcLookup<JcTypedField, JcTypedMethod>
147+
) : DelegatingLookup<JcTypedField, JcTypedMethod>(ext, delegate) {
148+
override fun lookupOf(feature: JcLookupExtFeature): JcLookup<JcTypedField, JcTypedMethod> = feature.lookup(type)
95149
}

jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcClassOrInterfaceImpl.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ class JcClassOrInterfaceImpl(
4242
else -> null // maybe we do not need to do right now
4343
}
4444

45-
override val lookup: JcLookup<JcField, JcMethod> = JcClassLookupImpl(this)
45+
override val lookup: JcLookup<JcField, JcMethod> = ClassDelegatingLookup(
46+
this,
47+
featuresChain.classLookups,
48+
JcClassLookupImpl(this)
49+
)
4650

4751
private val extensionData by lazy(PUBLICATION) {
4852
HashMap<String, Any>().also { map ->

jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList<JcRawInst>
306306

307307
override fun visitJcRawFieldRef(value: JcRawFieldRef): JcExpr {
308308
val type = value.declaringClass.asType() as JcClassType
309-
val field = type.lookup.field(value.fieldName, value.declaringClass)
309+
val field = type.lookup.field(value.fieldName, value.typeName)
310310
?: throw IllegalStateException("${type.typeName}#${value.fieldName} not found")
311311
return JcFieldRef(value.instance?.accept(this) as? JcValue, field)
312312
}

jacodb-core/src/main/kotlin/org/jacodb/impl/features/JcFeaturesChain.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ package org.jacodb.impl.features
1818

1919
import org.jacodb.api.JcClasspathFeature
2020
import org.jacodb.api.JcFeatureEvent
21+
import org.jacodb.api.JcLookupExtFeature
2122

2223
class JcFeaturesChain(val features: List<JcClasspathFeature>) {
2324

25+
val classLookups = features.filterIsInstance<JcLookupExtFeature>()
26+
2427
inline fun <reified T : JcClasspathFeature> run(call: (T) -> Unit) {
2528
for (feature in features) {
2629
if (feature is T) {

jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownClass.kt

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class JcUnknownClass(override var classpath: JcClasspath, name: String) : JcVirt
3838
}
3939

4040
class JcUnknownMethod(
41-
enclosingClass: JcUnknownClass,
41+
enclosingClass: JcClassOrInterface,
4242
name: String,
4343
description: String,
4444
returnType: TypeName,
@@ -51,17 +51,17 @@ class JcUnknownMethod(
5151
) {
5252

5353
companion object {
54-
fun method(type: JcUnknownClass, name: String, description: String): JcMethod {
54+
fun method(type: JcClassOrInterface, name: String, description: String): JcMethod {
5555
val methodType = Type.getMethodType(description)
5656
val returnType = TypeNameImpl(methodType.returnType.className.jcdbName())
5757
val paramsType = methodType.argumentTypes.map { TypeNameImpl(it.className.jcdbName()) }
5858
return JcUnknownMethod(type, name, description, returnType, paramsType)
5959
}
6060

61-
fun typedMethod(type: JcUnknownType, name: String, description: String): JcTypedMethod {
61+
fun typedMethod(type: JcClassType, name: String, description: String): JcTypedMethod {
6262
return JcTypedMethodImpl(
6363
type,
64-
method(type.jcClass as JcUnknownClass, name, description),
64+
method(type.jcClass, name, description),
6565
JcSubstitutor.empty
6666
)
6767
}
@@ -72,15 +72,15 @@ class JcUnknownMethod(
7272
}
7373
}
7474

75-
class JcUnknownField(enclosingClass: JcUnknownClass, name: String, type: TypeName) :
75+
class JcUnknownField(enclosingClass: JcClassOrInterface, name: String, type: TypeName) :
7676
JcVirtualFieldImpl(name, type = type) {
7777

7878
companion object {
7979

8080
fun typedField(type: JcClassType, name: String, fieldType: TypeName): JcTypedField {
8181
return JcTypedFieldImpl(
8282
type,
83-
JcUnknownField(type.jcClass as JcUnknownClass, name, fieldType),
83+
JcUnknownField(type.jcClass, name, fieldType),
8484
JcSubstitutor.empty
8585
)
8686
}
@@ -136,4 +136,42 @@ object UnknownClasses : JcClasspathExtFeature {
136136
}
137137
}
138138

139+
/**
140+
* Used for mocking of methods and fields refs that doesn't exist in code base of classpath
141+
* ```
142+
* class Bar {
143+
*
144+
* int x = 0;
145+
*
146+
* public void run() {
147+
* System.out.println("Hello world");
148+
* }
149+
* }
150+
*
151+
* class Foo extends Bar {
152+
*
153+
* Bar f = new Bar();
154+
*
155+
* public void call() {
156+
* System.out.println(f.y);
157+
* f.runSomething();
158+
* }
159+
* }
160+
* ```
161+
*
162+
* 3-address representation of bytecode for Foo class can't resolve `Bar#y` field and `Bar#runSomething`
163+
* method by default. With this feature such methods and fields will be resolved as JcUnknownField and JcUnknownMethod
164+
*/
165+
object UnknownClassMethodsAndFields : JcLookupExtFeature {
166+
167+
override fun lookup(clazz: JcClassOrInterface): JcLookup<JcField, JcMethod> {
168+
return JcUnknownClassLookup(clazz)
169+
}
170+
171+
override fun lookup(type: JcClassType): JcLookup<JcTypedField, JcTypedMethod> {
172+
return JcUnknownTypeLookup(type)
173+
}
174+
}
175+
176+
139177
val JcClasspath.isResolveAllToUnknown: Boolean get() = isInstalled(UnknownClasses)

jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class JcUnknownType(override var classpath: JcClasspath, private val name: Strin
5454
get() = Opcodes.ACC_PUBLIC
5555
}
5656

57-
class JcUnknownClassLookup(val clazz: JcUnknownClass) : JcLookup<JcField, JcMethod> {
57+
open class JcUnknownClassLookup(val clazz: JcClassOrInterface) : JcLookup<JcField, JcMethod> {
5858

5959
override fun specialMethod(name: String, description: String): JcMethod = method(name, description)
6060
override fun staticMethod(name: String, description: String): JcMethod = method(name, description)
@@ -69,7 +69,7 @@ class JcUnknownClassLookup(val clazz: JcUnknownClass) : JcLookup<JcField, JcMeth
6969

7070
}
7171

72-
class JcUnknownTypeLookup(val type: JcUnknownType) : JcLookup<JcTypedField, JcTypedMethod> {
72+
open class JcUnknownTypeLookup(val type: JcClassType) : JcLookup<JcTypedField, JcTypedMethod> {
7373

7474
override fun specialMethod(name: String, description: String): JcTypedMethod = method(name, description)
7575
override fun staticMethod(name: String, description: String): JcTypedMethod = method(name, description)

jacodb-core/src/main/kotlin/org/jacodb/impl/types/JcClassTypeImpl.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ import org.jacodb.api.*
2020
import org.jacodb.api.ext.findClass
2121
import org.jacodb.api.ext.packageName
2222
import org.jacodb.api.ext.toType
23+
import org.jacodb.impl.bytecode.TypeDelegatingLookup
2324
import org.jacodb.impl.types.signature.*
2425
import org.jacodb.impl.types.substition.JcSubstitutor
2526
import org.jacodb.impl.types.substition.substitute
2627
import kotlin.LazyThreadSafetyMode.PUBLICATION
2728

28-
open class JcClassTypeImpl(
29+
class JcClassTypeImpl(
2930
override val classpath: JcClasspath,
3031
val name: String,
3132
override val outerType: JcClassTypeImpl? = null,
@@ -52,7 +53,12 @@ open class JcClassTypeImpl(
5253

5354
private val resolutionImpl by lazy(PUBLICATION) { TypeSignature.withDeclarations(jcClass) as? TypeResolutionImpl }
5455
private val declaredTypeParameters by lazy(PUBLICATION) { jcClass.typeParameters }
55-
override val lookup: JcLookup<JcTypedField, JcTypedMethod> = JcClassTypeLookupImpl(this)
56+
57+
override val lookup: JcLookup<JcTypedField, JcTypedMethod> = TypeDelegatingLookup(
58+
this,
59+
classpath.features?.filterIsInstance<JcLookupExtFeature>().orEmpty(),
60+
JcClassTypeLookupImpl(this)
61+
)
5662

5763
override val jcClass: JcClassOrInterface get() = classpath.findClass(name)
5864

jacodb-core/src/test/kotlin/org/jacodb/testing/UnknownClassesTest.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import org.jacodb.api.ext.cfg.callExpr
2121
import org.jacodb.api.ext.cfg.fieldRef
2222
import org.jacodb.api.ext.findClass
2323
import org.jacodb.impl.features.classpaths.JcUnknownClass
24+
import org.jacodb.impl.features.classpaths.UnknownClassMethodsAndFields
2425
import org.jacodb.impl.features.classpaths.UnknownClasses
2526
import org.junit.jupiter.api.Assertions.assertNotNull
2627
import org.junit.jupiter.api.Assertions.assertTrue
2728
import org.junit.jupiter.api.Test
2829

2930
class UnknownClassesTest : BaseTest() {
3031

31-
companion object : WithDB(UnknownClasses)
32+
companion object : WithDB(UnknownClasses, UnknownClassMethodsAndFields)
3233

3334
@Test
3435
fun `unknown class is resolved`() {
@@ -64,6 +65,16 @@ class UnknownClassesTest : BaseTest() {
6465
}
6566
}
6667

68+
@Test
69+
fun `instructions with references to unknown fields and methods are resolved`() {
70+
val clazz = listOf(
71+
cp.findClass("PhantomDeclarationConsumer")
72+
)
73+
clazz.forEach {
74+
it.declaredMethods.forEach { it.assertCfg() }
75+
}
76+
}
77+
6778
private fun JcMethod.assertCfg(){
6879
val cfg = flowGraph()
6980
cfg.instructions.forEach {

0 commit comments

Comments
 (0)