From 9ff9cb177d97e718aa6630e22a9d43536c092a5c Mon Sep 17 00:00:00 2001 From: Mikhail Kosticyn Date: Mon, 27 May 2024 17:10:08 +0300 Subject: [PATCH 1/2] Resolving non-static lambda (#227) * [fix] fixed resolving non-static lambda --- .github/workflows/build-and-test.yml | 3 +- .../org/jacodb/impl/cfg/JcInstListBuilder.kt | 17 +++++++- .../jacodb/testing/cfg/InvokeDynamicTest.kt | 15 +++++++ .../testing/cfg/InvokeDynamicExamples.java | 43 +++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cb79cc5a5..e469e3608 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -5,6 +5,7 @@ on: pull_request: branches: - develop + - neo permissions: contents: read @@ -105,4 +106,4 @@ jobs: if: always() with: files: "**/build/test-results/**/*.xml" - check_name: "Lifecycle test results" \ No newline at end of file + check_name: "Lifecycle test results" diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index bf1cbf629..ea71ed486 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -285,9 +285,24 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList if (dynamicMethodType !is BsmMethodTypeArg) return null if (implementation !is BsmHandle) return null + val argTypes: List + val tag = implementation.tag + if (tag == 6) { + // Invoke static case + argTypes = implementation.argTypes + } else { + // Invoke non-static case + check(tag == 5 || tag == 7 || tag == 8 || tag == 9) { + "Unexpected tag for invoke dynamic $tag" + } + argTypes = implementation.argTypes.toMutableList() + // Adding 'this' type as first argument type + argTypes.add(0, implementation.declaringClass) + } + // Check implementation signature match (starts with) call site arguments for ((index, argType) in expr.callSiteArgTypes.withIndex()) { - if (argType != implementation.argTypes.getOrNull(index)) return null + if (argType != argTypes.getOrNull(index)) return null } val klass = implementation.declaringClass.asType() as JcClassType diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt index 78a0fc081..d07c0e3e7 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt @@ -16,9 +16,12 @@ package org.jacodb.testing.cfg +import org.jacodb.api.jvm.cfg.JcAssignInst +import org.jacodb.api.jvm.cfg.JcLambdaExpr import org.jacodb.api.jvm.ext.findClass import org.jacodb.testing.WithGlobalDB import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class InvokeDynamicTest : BaseInstructionsTest() { @@ -43,6 +46,18 @@ class InvokeDynamicTest : BaseInstructionsTest() { @Test fun `test complex invoke dynamic`() = runStaticMethod("testComplexInvokeDynamic") + @Test + fun `test resolving virtual lambda`() { + val clazz = cp.findClass() + val method = clazz.declaredMethods.find { it.name == "putAll" }!! + val instructions = method.instList + val first = instructions[0] as JcAssignInst + assertTrue(first.rhv is JcLambdaExpr) + val third = instructions[2] as JcAssignInst + assertTrue(third.rhv is JcLambdaExpr) + runStaticMethod("testNonStaticLambda") + } + private inline fun runStaticMethod(name: String) { val clazz = cp.findClass() diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java index 803b80180..289ab2b01 100644 --- a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java @@ -16,6 +16,8 @@ package org.jacodb.testing.cfg; +import java.util.HashMap; +import java.util.Map; import java.util.function.Function; public class InvokeDynamicExamples { @@ -95,4 +97,45 @@ public static String testComplexInvokeDynamic() { String actual = runComplexStringConcat("abc", 42); return expected.equals(actual) ? "OK" : "BAD"; } + + public static class CollectionWithInnerMap { + private final Map innerMap; + + public CollectionWithInnerMap() { + innerMap = new HashMap<>(); + } + + private void add(String key, String value) { + innerMap.put(key, value); + } + + public void putAll(Map map) { + map.forEach(this::removeBindingResultIfNecessary); + map.forEach(this::privateRemoveBindingResultIfNecessary); + innerMap.putAll(map); + } + + void removeBindingResultIfNecessary(String key, String value) { + if (!key.isEmpty()) { + add(key + "cde", value); + } + } + + private void privateRemoveBindingResultIfNecessary(String key, String value) { + if (!key.isEmpty()) { + add(key + "abc", value); + } + } + } + + public static String testNonStaticLambda() { + CollectionWithInnerMap collection = new CollectionWithInnerMap(); + Map map = new HashMap<>(); + map.put("abc", "cde"); + map.put("dead", "beef"); + collection.putAll(map); + String expected = "beef"; + String actual = collection.innerMap.get("deadabc"); + return expected.equals(actual) ? "OK" : "BAD"; + } } From 6c37ac4510c93ad6f4ce1feb9fd9056f2b3e8e53 Mon Sep 17 00:00:00 2001 From: Mikhail Kosticyn Date: Tue, 4 Jun 2024 19:42:01 +0300 Subject: [PATCH 2/2] [fix] fixed lambda expression with constructor call (#228) --- .../kotlin/org/jacodb/api/jvm/cfg/JcInst.kt | 1 + .../org/jacodb/impl/cfg/JcInstListBuilder.kt | 9 ++++++-- .../jacodb/testing/cfg/InvokeDynamicTest.kt | 3 +++ .../testing/cfg/InvokeDynamicExamples.java | 23 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/cfg/JcInst.kt b/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/cfg/JcInst.kt index 641ea16c2..96323371b 100644 --- a/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/cfg/JcInst.kt +++ b/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/cfg/JcInst.kt @@ -705,6 +705,7 @@ data class JcLambdaExpr( val callSiteArgTypes: List, val callSiteReturnType: JcType, val callSiteArgs: List, + val isNewInvokeSpecial: Boolean, ) : JcCallExpr { override val method get() = bsmRef.method diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index ea71ed486..b92491af1 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -287,12 +287,16 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList val argTypes: List val tag = implementation.tag + var isNewInvokeSpecial = false if (tag == 6) { // Invoke static case argTypes = implementation.argTypes + } else if (tag == 8) { + isNewInvokeSpecial = true + argTypes = implementation.argTypes } else { // Invoke non-static case - check(tag == 5 || tag == 7 || tag == 8 || tag == 9) { + check(tag == 5 || tag == 7 || tag == 9) { "Unexpected tag for invoke dynamic $tag" } argTypes = implementation.argTypes.toMutableList() @@ -318,7 +322,8 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList expr.callSiteMethodName, expr.callSiteArgTypes.map { it.asType() }, expr.callSiteReturnType.asType(), - expr.callSiteArgs.map { it.accept(this) as JcValue } + expr.callSiteArgs.map { it.accept(this) as JcValue }, + isNewInvokeSpecial ) } diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt index d07c0e3e7..fdb34d65b 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InvokeDynamicTest.kt @@ -58,6 +58,9 @@ class InvokeDynamicTest : BaseInstructionsTest() { runStaticMethod("testNonStaticLambda") } + @Test + fun `invoke dynamic constructor`() = runStaticMethod("testInvokeDynamicConstructor") + private inline fun runStaticMethod(name: String) { val clazz = cp.findClass() diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java index 289ab2b01..ac9ab4bce 100644 --- a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/InvokeDynamicExamples.java @@ -98,6 +98,29 @@ public static String testComplexInvokeDynamic() { return expected.equals(actual) ? "OK" : "BAD"; } + static class A { + private final String prefix; + + public A(String prefix) { + this.prefix = prefix; + } + + @Override + public String toString() { + return prefix + "456"; + } + } + + private static String invokeDynamicConstructor(Function f){ + A a = f.apply("123"); + return a.toString(); + } + + public static String testInvokeDynamicConstructor() { + final String result = invokeDynamicConstructor(A::new); + return "123456".equals(result) ? "OK" : "BAD"; + } + public static class CollectionWithInnerMap { private final Map innerMap;