[go: nahoru, domu]

Avoid producing an invalid recompose lambda when skipping

If a function is skipped the default parameters are not updated using
the default expressions but the recompose function could capture
these incorrect values for later invocation.

The composer now marks the recompose scope as unused before
recomposing a function preventing this.

Fixes: b/161892016
Test: new test
Change-Id: Ifbf8fe5a6be1b56510a47105acade2301e7928a1
diff --git a/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/test/RecomposerTests.kt b/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/test/RecomposerTests.kt
index 590efdf..4b0cb26 100644
--- a/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/test/RecomposerTests.kt
+++ b/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/test/RecomposerTests.kt
@@ -40,6 +40,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -422,6 +423,44 @@
         }
     }
 
+    @Test // regression test for b/161892016
+    fun testMultipleRecompose() {
+        class A
+
+        var state1 by mutableStateOf(1)
+        var state2 by mutableStateOf(1)
+
+        @Composable fun validate(a: A?) {
+            assertNotNull(a)
+        }
+
+        @Composable fun use(@Suppress("UNUSED_PARAMETER") i: Int) { }
+
+        @Composable fun useA(a: A = A()) {
+            validate(a)
+            use(state2)
+        }
+
+        @Composable fun test() {
+            use(state1)
+            useA()
+        }
+
+        compose {
+            test()
+        }.then {
+            // Recompose test() skipping useA()
+            state1 = 2
+        }.then {
+            // Recompose useA(). In the bug this causes validate() to be passed a null because
+            // the recompose scope is updated with a lambda that captures the parameters when the
+            // default parameter expressions are skipped.
+            state2 = 2
+        }.then {
+            // force recompose to validate a.
+        }
+    }
+
     @Test
     @OptIn(ExperimentalComposeApi::class)
     fun testFrameTransition() {
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
index 8cf543b..186769a 100644
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
@@ -1761,6 +1761,7 @@
     @ComposeCompilerApi
     fun skipToGroupEnd() {
         check(groupNodeCount == 0) { "No nodes can be emitted before calling skipAndEndGroup" }
+        currentRecomposeScope?.used = false
         if (invalidations.isEmpty()) {
             skipReaderToGroupEnd()
         } else {