Merge "Fix kdoc issue in Serializer.kt" into androidx-main
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
index 7eda4fc..538cba8 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
@@ -108,8 +108,44 @@
)
}
}
+
+ /**
+ * Only used by group-internal tests
+ *
+ * Most minimal config possible
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ class MinimalTest(
+ private val appTagPackages: List<String>
+ ) : PerfettoConfig(isTextProto = false) {
+ @RequiresApi(23)
+ override fun writeTo(file: File) {
+ file.writeBytes(
+ configOf(
+ listOf(
+ minimalAtraceDataSource(atraceApps = appTagPackages)
+ )
+ ).validateAndEncode()
+ )
+ }
+ }
}
+private fun minimalAtraceDataSource(
+ atraceApps: List<String>
+) = TraceConfig.DataSource(
+ config = DataSourceConfig(
+ name = "linux.ftrace",
+ target_buffer = 0,
+ ftrace_config = FtraceConfig(
+ ftrace_events = emptyList(),
+ atrace_categories = emptyList(),
+ atrace_apps = atraceApps,
+ compact_sched = null
+ )
+ )
+)
+
private fun ftraceDataSource(
atraceApps: List<String>
) = TraceConfig.DataSource(
@@ -315,6 +351,27 @@
return sources
}
+private fun configOf(
+ dataSources: List<TraceConfig.DataSource>
+) = TraceConfig(
+ buffers = listOf(
+ BufferConfig(size_kb = 32768, FillPolicy.RING_BUFFER),
+ BufferConfig(size_kb = 4096, FillPolicy.RING_BUFFER)
+ ),
+ data_sources = dataSources,
+ // periodically dump to file, so we don't overrun our ring buffer
+ // buffers are expected to be big enough for 5 seconds, so conservatively set 2.5 dump
+ write_into_file = true,
+ file_write_period_ms = 2500,
+
+ // multiple of file_write_period_ms, enables trace processor to work in batches
+ flush_period_ms = 5000,
+
+ // reduce timeout to reduce trace capture overhead when devices have data source issues
+ // See b/323601788 and b/307649002.
+ data_source_stop_timeout_ms = 2500,
+)
+
/**
* Config for perfetto.
*
@@ -346,24 +403,7 @@
config = stackSamplingConfig
)
}
- return TraceConfig(
- buffers = listOf(
- BufferConfig(size_kb = 32768, FillPolicy.RING_BUFFER),
- BufferConfig(size_kb = 4096, FillPolicy.RING_BUFFER)
- ),
- data_sources = dataSources,
- // periodically dump to file, so we don't overrun our ring buffer
- // buffers are expected to be big enough for 5 seconds, so conservatively set 2.5 dump
- write_into_file = true,
- file_write_period_ms = 2500,
-
- // multiple of file_write_period_ms, enables trace processor to work in batches
- flush_period_ms = 5000,
-
- // reduce timeout to reduce trace capture overhead when devices have data source issues
- // See b/323601788 and b/307649002.
- data_source_stop_timeout_ms = 2500,
- )
+ return configOf(dataSources)
}
@RequiresApi(21) // needed for shell access
diff --git a/benchmark/benchmark-macro-junit4/api/current.txt b/benchmark/benchmark-macro-junit4/api/current.txt
index d4c3495..e06bf57 100644
--- a/benchmark/benchmark-macro-junit4/api/current.txt
+++ b/benchmark/benchmark-macro-junit4/api/current.txt
@@ -19,6 +19,10 @@
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, @IntRange(from=1L) int iterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, @IntRange(from=1L) int iterations, optional kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> setupBlock, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, optional androidx.benchmark.macro.CompilationMode compilationMode, @IntRange(from=1L) int iterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, optional kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> setupBlock, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, optional androidx.benchmark.macro.CompilationMode compilationMode, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
}
diff --git a/benchmark/benchmark-macro-junit4/api/restricted_current.txt b/benchmark/benchmark-macro-junit4/api/restricted_current.txt
index d4c3495..e06bf57 100644
--- a/benchmark/benchmark-macro-junit4/api/restricted_current.txt
+++ b/benchmark/benchmark-macro-junit4/api/restricted_current.txt
@@ -19,6 +19,10 @@
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, @IntRange(from=1L) int iterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, @IntRange(from=1L) int iterations, optional kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> setupBlock, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, optional androidx.benchmark.macro.CompilationMode compilationMode, @IntRange(from=1L) int iterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, optional androidx.benchmark.macro.CompilationMode compilationMode, optional androidx.benchmark.macro.StartupMode? startupMode, optional kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> setupBlock, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, optional androidx.benchmark.macro.CompilationMode compilationMode, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
+ method @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, androidx.benchmark.perfetto.PerfettoConfig perfettoConfig, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
method public void measureRepeated(String packageName, java.util.List<? extends androidx.benchmark.macro.Metric> metrics, @IntRange(from=1L) int iterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> measureBlock);
}
diff --git a/benchmark/benchmark-macro-junit4/build.gradle b/benchmark/benchmark-macro-junit4/build.gradle
index c99550b..f2f45e0 100644
--- a/benchmark/benchmark-macro-junit4/build.gradle
+++ b/benchmark/benchmark-macro-junit4/build.gradle
@@ -59,6 +59,17 @@
// DexMaker has it"s own MockMaker
}
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ // Enable using experimental APIs from within same version group
+ freeCompilerArgs += [
+ "-opt-in=androidx.benchmark.macro.ExperimentalMetricApi",
+ "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi",
+ "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi"
+ ]
+ }
+}
+
androidx {
name = "Benchmark - Macrobenchmark JUnit4"
publish = Publish.SNAPSHOT_AND_RELEASE
diff --git a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt
index d8a01b7..6ca8df5 100644
--- a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt
+++ b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt
@@ -24,6 +24,8 @@
import androidx.benchmark.macro.Metric
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.macrobenchmarkWithStartupMode
+import androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi
+import androidx.benchmark.perfetto.PerfettoConfig
import androidx.test.rule.GrantPermissionRule
import org.junit.Assume.assumeTrue
import org.junit.rules.RuleChain
@@ -94,7 +96,7 @@
* @param measureBlock The block performing app actions to benchmark each iteration.
*/
@JvmOverloads
- public fun measureRepeated(
+ fun measureRepeated(
packageName: String,
metrics: List<Metric>,
compilationMode: CompilationMode = CompilationMode.DEFAULT,
@@ -113,6 +115,78 @@
compilationMode = compilationMode,
iterations = iterations,
startupMode = startupMode,
+ perfettoConfig = null,
+ setupBlock = setupBlock,
+ measureBlock = measureBlock
+ )
+ }
+
+ /**
+ * Measure behavior of the specified [packageName] given a set of [metrics], with a custom
+ * [PerfettoConfig].
+ *
+ * This performs a macrobenchmark with the below control flow:
+ * ```
+ * resetAppCompilation()
+ * compile(compilationMode)
+ * repeat(iterations) {
+ * setupBlock()
+ * captureTraceAndMetrics {
+ * measureBlock()
+ * }
+ * }
+ * ```
+ *
+ * Note that a custom [PerfettoConfig]s may result in built-in [Metric]s not working.
+ *
+ * You can see the PerfettoConfig used by a trace (as a text proto) by opening the trace in
+ * [ui.perfetto.dev](http://ui.perfetto.dev), and selecting `Info and Stats` view on the left
+ * panel. You can also generate a custom text proto config by selecting `Record new trace` on
+ * the same panel, selecting recording options, and then clicking `Recording command` to access
+ * the generated text proto.
+ *
+ * @param packageName ApplicationId / Application manifest package name of the app for
+ * which profiles are generated.
+ * @param metrics List of metrics to measure.
+ * @param compilationMode Mode of compilation used before capturing measurement, such as
+ * [CompilationMode.Partial], defaults to [CompilationMode.DEFAULT].
+ * @param startupMode Optional mode to force app launches performed with
+ * [MacrobenchmarkScope.startActivityAndWait] (and similar variants) to be of the assigned
+ * type. For example, `COLD` launches kill the process before the measureBlock, to ensure
+ * startups will go through full process creation. Generally, leave as null for non-startup
+ * benchmarks.
+ * @param iterations Number of times the [measureBlock] will be run during measurement. Note
+ * that total iteration count may not match, due to warmup iterations needed for the
+ * [compilationMode].
+ * @param perfettoConfig Configuration for Perfetto trace capture during each iteration. Note
+ * that insufficient or invalid configs may result in built-in [Metric]s not working.
+ * @param setupBlock The block performing app actions each iteration, prior to the
+ * [measureBlock]. For example, navigating to a UI where scrolling will be measured.
+ * @param measureBlock The block performing app actions to benchmark each iteration.
+ */
+ @ExperimentalPerfettoCaptureApi
+ @JvmOverloads
+ fun measureRepeated(
+ packageName: String,
+ metrics: List<Metric>,
+ @IntRange(from = 1)
+ iterations: Int,
+ perfettoConfig: PerfettoConfig,
+ compilationMode: CompilationMode = CompilationMode.DEFAULT,
+ startupMode: StartupMode? = null,
+ setupBlock: MacrobenchmarkScope.() -> Unit = {},
+ measureBlock: MacrobenchmarkScope.() -> Unit,
+ ) {
+ macrobenchmarkWithStartupMode(
+ uniqueName = currentDescription.toUniqueName(),
+ className = currentDescription.className,
+ testName = currentDescription.methodName,
+ packageName = packageName,
+ metrics = metrics,
+ compilationMode = compilationMode,
+ iterations = iterations,
+ perfettoConfig = perfettoConfig,
+ startupMode = startupMode,
setupBlock = setupBlock,
measureBlock = measureBlock
)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkTest.kt
index 09a6c7d..50b1662 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkTest.kt
@@ -16,8 +16,10 @@
package androidx.benchmark.macro
+import android.annotation.SuppressLint
import androidx.annotation.RequiresApi
import androidx.benchmark.DeviceInfo
+import androidx.benchmark.perfetto.PerfettoConfig
import androidx.benchmark.perfetto.PerfettoHelper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -55,6 +57,7 @@
compilationMode = CompilationMode.Ignore(),
iterations = 1,
startupMode = null,
+ perfettoConfig = null,
setupBlock = {},
measureBlock = {}
)
@@ -74,6 +77,7 @@
compilationMode = CompilationMode.Ignore(),
iterations = 0, // invalid
startupMode = null,
+ perfettoConfig = null,
setupBlock = {},
measureBlock = {}
)
@@ -101,6 +105,7 @@
compilationMode = CompilationMode.DEFAULT,
iterations = 2,
startupMode = startupMode,
+ perfettoConfig = null,
setupBlock = {
opOrder += Block.Setup
setupIterations += iteration
@@ -157,6 +162,49 @@
@Test
fun callbackBehavior_hot() = validateCallbackBehavior(StartupMode.HOT)
+ @SuppressLint("BanThreadSleep") // need non-zero duration to assert sum, regardless of clock
+ private fun validateSlicesCustomConfig(includeMacroAppTag: Boolean) {
+ val atraceApps = if (includeMacroAppTag) {
+ listOf(Packages.TEST)
+ } else {
+ emptyList()
+ }
+ val measurements = macrobenchmarkWithStartupMode(
+ uniqueName = "MacrobenchmarkTest#validateSlicesCustomConfig",
+ className = "MacrobenchmarkTest",
+ testName = "validateCallbackBehavior",
+ packageName = Packages.TARGET,
+ // disable targetPackageOnly filter, since this process emits the event
+ metrics = listOf(TraceSectionMetric(TRACE_LABEL, targetPackageOnly = false)),
+ compilationMode = CompilationMode.DEFAULT,
+ iterations = 3,
+ startupMode = null,
+ perfettoConfig = PerfettoConfig.MinimalTest(atraceApps),
+ setupBlock = { },
+ measureBlock = {
+ trace(TRACE_LABEL) {
+ Thread.sleep(2)
+ }
+ }
+ ).metrics[TRACE_LABEL + "SumMs"]!!.runs
+
+ assertEquals(3, measurements.size)
+
+ if (includeMacroAppTag) {
+ assertTrue(measurements.all { it > 0.0 })
+ } else {
+ assertEquals(listOf(0.0, 0.0, 0.0), measurements)
+ }
+ }
+
+ @LargeTest
+ @Test
+ fun customConfig_thisProcess() = validateSlicesCustomConfig(includeMacroAppTag = true)
+
+ @LargeTest
+ @Test
+ fun customConfig_noProcess() = validateSlicesCustomConfig(includeMacroAppTag = false)
+
companion object {
const val TRACE_LABEL = "MacrobencharkTestTraceLabel"
}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 8203ef7..92eecb5 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -35,6 +35,7 @@
import androidx.benchmark.conditionalError
import androidx.benchmark.inMemoryTrace
import androidx.benchmark.json.BenchmarkData
+import androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi
import androidx.benchmark.perfetto.PerfettoCapture.PerfettoSdkConfig
import androidx.benchmark.perfetto.PerfettoCapture.PerfettoSdkConfig.InitialProcessState
import androidx.benchmark.perfetto.PerfettoCaptureWrapper
@@ -190,6 +191,7 @@
*
* This function is a building block for public testing APIs
*/
+@ExperimentalPerfettoCaptureApi
private fun macrobenchmark(
uniqueName: String,
className: String,
@@ -200,10 +202,11 @@
iterations: Int,
launchWithClearTask: Boolean,
startupModeMetricHint: StartupMode?,
+ perfettoConfig: PerfettoConfig?,
perfettoSdkConfig: PerfettoSdkConfig?,
setupBlock: MacrobenchmarkScope.() -> Unit,
measureBlock: MacrobenchmarkScope.() -> Unit
-) {
+): BenchmarkData.TestResult {
require(iterations > 0) {
"Require iterations > 0 (iterations = $iterations)"
}
@@ -276,7 +279,7 @@
val fileLabel = "${uniqueName}_iter$iterString"
val tracePath = perfettoCollector.record(
fileLabel = fileLabel,
- config = PerfettoConfig.Benchmark(
+ config = perfettoConfig ?: PerfettoConfig.Benchmark(
/**
* Prior to API 24, every package name was joined into a single setprop
* which can overflow, and disable *ALL* app level tracing.
@@ -405,18 +408,18 @@
} + methodTracingResultFiles).map {
BenchmarkData.TestResult.ProfilerOutput(it)
}
- ResultWriter.appendTestResult(
- BenchmarkData.TestResult(
- className = className,
- name = testName,
- totalRunTimeNs = System.nanoTime() - startTime,
- metrics = measurements.singleMetrics + measurements.sampledMetrics,
- repeatIterations = iterations,
- thermalThrottleSleepSeconds = 0,
- warmupIterations = warmupIterations,
- profilerOutputs = mergedProfilerOutputs
- )
+ val testResult = BenchmarkData.TestResult(
+ className = className,
+ name = testName,
+ totalRunTimeNs = System.nanoTime() - startTime,
+ metrics = measurements.singleMetrics + measurements.sampledMetrics,
+ repeatIterations = iterations,
+ thermalThrottleSleepSeconds = 0,
+ warmupIterations = warmupIterations,
+ profilerOutputs = mergedProfilerOutputs
)
+ ResultWriter.appendTestResult(testResult)
+ return testResult
} finally {
scope.killProcess()
}
@@ -426,6 +429,7 @@
* Run a macrobenchmark with the specified StartupMode
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalPerfettoCaptureApi
fun macrobenchmarkWithStartupMode(
uniqueName: String,
className: String,
@@ -434,10 +438,11 @@
metrics: List<Metric>,
compilationMode: CompilationMode,
iterations: Int,
+ perfettoConfig: PerfettoConfig?,
startupMode: StartupMode?,
setupBlock: MacrobenchmarkScope.() -> Unit,
measureBlock: MacrobenchmarkScope.() -> Unit
-) {
+): BenchmarkData.TestResult {
val perfettoSdkConfig =
if (Arguments.perfettoSdkTracingEnable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
PerfettoSdkConfig(
@@ -449,7 +454,7 @@
}
)
} else null
- macrobenchmark(
+ return macrobenchmark(
uniqueName = uniqueName,
className = className,
testName = testName,
@@ -458,6 +463,7 @@
compilationMode = compilationMode,
iterations = iterations,
startupModeMetricHint = startupMode,
+ perfettoConfig = perfettoConfig,
perfettoSdkConfig = perfettoSdkConfig,
setupBlock = {
if (startupMode == StartupMode.COLD) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index dbbc781..51df347 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -340,6 +340,6 @@
SourceOption(ComposePluginId, "sourceInformation"),
MetricsOption(ComposePluginId, "metricsDestination"),
ReportsOption(ComposePluginId, "reportsDestination"),
- StrongSkippingOption(ComposePluginId, "experimentalStrongSkipping"),
+ StrongSkippingOption(ComposePluginId, "strongSkipping"),
NonSkippingGroupOption(ComposePluginId, "nonSkippingGroupOptimization")
}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/RenderInTransitionOverlayNodeElement.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/RenderInTransitionOverlayNodeElement.kt
index 74e4d20..e9118d6 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/RenderInTransitionOverlayNodeElement.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/RenderInTransitionOverlayNodeElement.kt
@@ -66,9 +66,9 @@
override fun equals(other: Any?): Boolean {
if (other is RenderInTransitionOverlayNodeElement) {
return sharedTransitionScope == other.sharedTransitionScope &&
- renderInOverlay == other.renderInOverlay &&
+ renderInOverlay === other.renderInOverlay &&
zIndexInOverlay == other.zIndexInOverlay &&
- clipInOverlay == other.clipInOverlay
+ clipInOverlay === other.clipInOverlay
}
return false
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index 4be9688..c445253 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -166,9 +166,16 @@
allowMultipleOccurrences = false
)
val STRONG_SKIPPING_OPTION = CliOption(
+ "strongSkipping",
+ "<true|false>",
+ "Enable strong skipping mode",
+ required = false,
+ allowMultipleOccurrences = false
+ )
+ val EXPERIMENTAL_STRONG_SKIPPING_OPTION = CliOption(
"experimentalStrongSkipping",
"<true|false>",
- "Enable experimental strong skipping mode",
+ "Deprecated - Use strongSkipping instead",
required = false,
allowMultipleOccurrences = false
)
@@ -200,6 +207,7 @@
NON_SKIPPING_GROUP_OPTIMIZATION_ENABLED_OPTION,
SUPPRESS_KOTLIN_VERSION_CHECK_ENABLED_OPTION,
DECOYS_ENABLED_OPTION,
+ EXPERIMENTAL_STRONG_SKIPPING_OPTION,
STRONG_SKIPPING_OPTION,
STABLE_CONFIG_PATH_OPTION,
TRACE_MARKERS_OPTION,
@@ -250,6 +258,18 @@
ComposeConfiguration.DECOYS_ENABLED_KEY,
value == "true"
)
+ EXPERIMENTAL_STRONG_SKIPPING_OPTION -> {
+ val msgCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
+ msgCollector?.report(
+ CompilerMessageSeverity.WARNING,
+ "${EXPERIMENTAL_STRONG_SKIPPING_OPTION.optionName} is deprecated." +
+ " Use ${STRONG_SKIPPING_OPTION.optionName} instead."
+ )
+ configuration.put(
+ ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY,
+ value == "true"
+ )
+ }
STRONG_SKIPPING_OPTION -> configuration.put(
ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY,
value == "true"
@@ -473,6 +493,7 @@
)
val useK2 = configuration.languageVersionSettings.languageVersion.usesK2
+
val strongSkippingEnabled = configuration.get(
ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY,
false
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
index 4fa34b4..3fc7a65 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
@@ -217,7 +217,7 @@
if (this === other) return true
val otherModifier = other as? OffsetPxElement ?: return false
- return offset == otherModifier.offset &&
+ return offset === otherModifier.offset &&
rtlAware == otherModifier.rtlAware
}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index e011e59..7467caf 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -559,7 +559,7 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifier = other as? WithAlignmentLineBlockElement ?: return false
- return block == otherModifier.block
+ return block === otherModifier.block
}
override fun hashCode(): Int = block.hashCode()
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt
index 2a93e7a1..e35dd4b 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt
@@ -249,7 +249,7 @@
return false
}
- return other.block == block
+ return other.block === block
}
override fun hashCode(): Int = block.hashCode()
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt
index 9c83dd3..4d2cc98 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt
@@ -161,7 +161,7 @@
if (other !is DerivedWidthModifier) {
return false
}
- return insets == other.insets && widthCalc == other.widthCalc
+ return insets == other.insets && widthCalc === other.widthCalc
}
override fun hashCode(): Int = 31 * insets.hashCode() + widthCalc.hashCode()
@@ -206,7 +206,7 @@
if (other !is DerivedHeightModifier) {
return false
}
- return insets == other.insets && heightCalc == other.heightCalc
+ return insets == other.insets && heightCalc === other.heightCalc
}
override fun hashCode(): Int = 31 * insets.hashCode() + heightCalc.hashCode()
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
index 59e1459..186bdd2 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
@@ -199,15 +199,15 @@
if (this === other) return true
if (other !is MagnifierElement) return false
- if (sourceCenter != other.sourceCenter) return false
- if (magnifierCenter != other.magnifierCenter) return false
+ if (sourceCenter !== other.sourceCenter) return false
+ if (magnifierCenter !== other.magnifierCenter) return false
if (zoom != other.zoom) return false
if (useTextDefault != other.useTextDefault) return false
if (size != other.size) return false
if (cornerRadius != other.cornerRadius) return false
if (elevation != other.elevation) return false
if (clippingEnabled != other.clippingEnabled) return false
- if (onSizeChanged != other.onSizeChanged) return false
+ if (onSizeChanged !== other.onSizeChanged) return false
if (platformMagnifierFactory != other.platformMagnifierFactory) return false
return true
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PreferKeepClear.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PreferKeepClear.android.kt
index 346ba634b..e6bd917 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PreferKeepClear.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PreferKeepClear.android.kt
@@ -84,7 +84,7 @@
override fun equals(other: Any?): Boolean {
if (other !is PreferKeepClearNode) return false
- return clearRect == other.rect
+ return clearRect === other.rect
}
override fun InspectorInfo.inspectableProperties() {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/SystemGestureExclusion.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/SystemGestureExclusion.android.kt
index a9b7f5d..77699fa 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/SystemGestureExclusion.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/SystemGestureExclusion.android.kt
@@ -84,7 +84,7 @@
override fun equals(other: Any?): Boolean {
if (other !is ExcludeFromSystemGestureElement) return false
- return exclusion == other.exclusion
+ return exclusion === other.exclusion
}
override fun InspectorInfo.inspectableProperties() {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt
index b4a18c5..4545e0a 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt
@@ -65,7 +65,7 @@
override fun hashCode() = 31 * callback.hashCode()
override fun equals(other: Any?) =
- (this === other) or ((other is HandwritingDelegatorElement) && callback == other.callback)
+ (this === other) or ((other is HandwritingDelegatorElement) && callback === other.callback)
override fun InspectorInfo.inspectableProperties() {
name = "handwritingDelegator"
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 3981ac6..2cea5ef 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -514,7 +514,7 @@
if (enabled != other.enabled) return false
if (onClickLabel != other.onClickLabel) return false
if (role != other.role) return false
- if (onClick != other.onClick) return false
+ if (onClick !== other.onClick) return false
return true
}
@@ -592,10 +592,10 @@
if (enabled != other.enabled) return false
if (onClickLabel != other.onClickLabel) return false
if (role != other.role) return false
- if (onClick != other.onClick) return false
+ if (onClick !== other.onClick) return false
if (onLongClickLabel != other.onLongClickLabel) return false
- if (onLongClick != other.onLongClick) return false
- if (onDoubleClick != other.onDoubleClick) return false
+ if (onLongClick !== other.onLongClick) return false
+ if (onDoubleClick !== other.onDoubleClick) return false
return true
}
@@ -826,7 +826,7 @@
resetPointerInputHandling = true
}
- if (this.onLongClick != onLongClick) {
+ if (this.onLongClick !== onLongClick) {
this.onLongClick = onLongClick
invalidateSemantics()
}
@@ -1225,9 +1225,9 @@
if (enabled != other.enabled) return false
if (role != other.role) return false
if (onLongClickLabel != other.onLongClickLabel) return false
- if (onLongClick != other.onLongClick) return false
+ if (onLongClick !== other.onLongClick) return false
if (onClickLabel != other.onClickLabel) return false
- if (onClick != other.onClick) return false
+ if (onClick !== other.onClick) return false
return true
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
index 01f5f64..df7af80 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
@@ -55,7 +55,7 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifier = other as? FocusedBoundsObserverElement ?: return false
- return onPositioned == otherModifier.onPositioned
+ return onPositioned === otherModifier.onPositioned
}
override fun InspectorInfo.inspectableProperties() {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
index 2a082d1..2a14342 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
@@ -79,7 +79,7 @@
if (other !is DropTargetElement) return false
if (target != other.target) return false
- return shouldStartDragAndDrop == other.shouldStartDragAndDrop
+ return shouldStartDragAndDrop === other.shouldStartDragAndDrop
}
override fun hashCode(): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
index 1e4abd9..297c21c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
@@ -116,7 +116,7 @@
other as TransformableElement
if (state != other.state) return false
- if (canPan != other.canPan) return false
+ if (canPan !== other.canPan) return false
if (lockRotationOnZoomPan != other.lockRotationOnZoomPan) return false
if (enabled != other.enabled) return false
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index 18a65d3..1920744 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -89,7 +89,7 @@
if (this === other) return true
if (other !is LazyLayoutSemanticsModifier) return false
- if (itemProviderLambda != other.itemProviderLambda) return false
+ if (itemProviderLambda !== other.itemProviderLambda) return false
if (state != other.state) return false
if (orientation != other.orientation) return false
if (userScrollEnabled != other.userScrollEnabled) return false
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
index caa756a..70d94a9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
@@ -203,7 +203,7 @@
if (indicationNodeFactory != other.indicationNodeFactory) return false
if (enabled != other.enabled) return false
if (role != other.role) return false
- if (onClick != other.onClick) return false
+ if (onClick !== other.onClick) return false
return true
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index b75e3c1..bcaa2ed 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -197,7 +197,7 @@
if (indicationNodeFactory != other.indicationNodeFactory) return false
if (enabled != other.enabled) return false
if (role != other.role) return false
- if (onValueChange != other.onValueChange) return false
+ if (onValueChange !== other.onValueChange) return false
return true
}
@@ -433,7 +433,7 @@
if (indicationNodeFactory != other.indicationNodeFactory) return false
if (enabled != other.enabled) return false
if (role != other.role) return false
- if (onClick != other.onClick) return false
+ if (onClick !== other.onClick) return false
return true
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt
index e3a65a3..79ca230 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt
@@ -46,7 +46,7 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- return (other as? GenericShape)?.builder == builder
+ return (other as? GenericShape)?.builder === builder
}
override fun hashCode(): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardActions.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardActions.kt
index d5112f2..428c7b8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardActions.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardActions.kt
@@ -74,12 +74,12 @@
if (this === other) return true
if (other !is KeyboardActions) return false
- return onDone == other.onDone &&
- onGo == other.onGo &&
- onNext == other.onNext &&
- onPrevious == other.onPrevious &&
- onSearch == other.onSearch &&
- onSend == other.onSend
+ return onDone === other.onDone &&
+ onGo === other.onGo &&
+ onNext === other.onNext &&
+ onPrevious === other.onPrevious &&
+ onSearch === other.onSearch &&
+ onSend === other.onSend
}
override fun hashCode(): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt
index fd739d6..7a1cd33 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt
@@ -93,14 +93,14 @@
// these are equally unlikely to change
if (fontFamilyResolver != other.fontFamilyResolver) return false
- if (onTextLayout != other.onTextLayout) return false
+ if (onTextLayout !== other.onTextLayout) return false
if (overflow != other.overflow) return false
if (softWrap != other.softWrap) return false
if (maxLines != other.maxLines) return false
if (minLines != other.minLines) return false
// these never change, but check anyway for correctness
- if (onPlaceholderLayout != other.onPlaceholderLayout) return false
+ if (onPlaceholderLayout !== other.onPlaceholderLayout) return false
if (selectionController != other.selectionController) return false
return true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt
index f8853ad..6525c51 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt
@@ -102,15 +102,15 @@
// these are equally unlikely to change
if (fontFamilyResolver != other.fontFamilyResolver) return false
- if (onTextLayout != other.onTextLayout) return false
- if (onShowTranslation != other.onShowTranslation) return false
+ if (onTextLayout !== other.onTextLayout) return false
+ if (onShowTranslation !== other.onShowTranslation) return false
if (overflow != other.overflow) return false
if (softWrap != other.softWrap) return false
if (maxLines != other.maxLines) return false
if (minLines != other.minLines) return false
// these never change, but check anyway for correctness
- if (onPlaceholderLayout != other.onPlaceholderLayout) return false
+ if (onPlaceholderLayout !== other.onPlaceholderLayout) return false
if (selectionController != other.selectionController) return false
return true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
index aa99015..9c3e8a6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
@@ -209,12 +209,12 @@
): Boolean {
var changed = false
- if (this.onTextLayout != onTextLayout) {
+ if (this.onTextLayout !== onTextLayout) {
this.onTextLayout = onTextLayout
changed = true
}
- if (this.onPlaceholderLayout != onPlaceholderLayout) {
+ if (this.onPlaceholderLayout !== onPlaceholderLayout) {
this.onPlaceholderLayout = onPlaceholderLayout
changed = true
}
@@ -224,7 +224,7 @@
changed = true
}
- if (this.onShowTranslation != onShowTranslation) {
+ if (this.onShowTranslation !== onShowTranslation) {
this.onShowTranslation = onShowTranslation
changed = true
}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 43f6253..36ecaff 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -37,6 +37,7 @@
CommonModuleIncompatibilityDetector.REFERENCE_ISSUE,
CommonModuleIncompatibilityDetector.EXTENDS_LAMBDA_ISSUE,
PrimitiveInCollectionDetector.ISSUE,
+ LambdaStructuralEqualityDetector.ISSUE
)
}
override val vendor = Vendor(
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt
new file mode 100644
index 0000000..6d7c0c0
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.EnumSet
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.uast.UBinaryExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UastBinaryOperator
+
+/**
+ * Lint [Detector] to ensure that lambda instances are compared referentially, instead of
+ * structurally.
+ *
+ * This is needed as function references (::lambda) do not consider their capture scope in their
+ * equals implementation. This means that structural equality can return true, even if the lambdas
+ * are different references with a different capture scope. Instead, lambdas should be compared
+ * referentially (=== or !==) to avoid this issue.
+ */
+class LambdaStructuralEqualityDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes() = listOf(
+ UBinaryExpression::class.java,
+ UCallExpression::class.java
+ )
+
+ override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+ override fun visitBinaryExpression(node: UBinaryExpression) {
+ val op = node.operator
+ if (op == UastBinaryOperator.EQUALS ||
+ op == UastBinaryOperator.NOT_EQUALS) {
+ val left = node.leftOperand.sourcePsi as? KtExpression ?: return
+ val right = node.rightOperand.sourcePsi as? KtExpression ?: return
+ if (left.isFunctionType() && right.isFunctionType()) {
+ val replacement = if (op == UastBinaryOperator.EQUALS) "===" else "!=="
+ context.report(
+ ISSUE,
+ node.operatorIdentifier,
+ context.getNameLocation(node.operatorIdentifier ?: node),
+ BriefDescription,
+ LintFix.create()
+ .replace()
+ .name("Change to $replacement")
+ .text(op.text)
+ .with(replacement)
+ .autoFix()
+ .build()
+ )
+ }
+ }
+ }
+
+ override fun visitCallExpression(node: UCallExpression) {
+ if (node.methodName == "equals") {
+ val left = node.receiver?.sourcePsi as? KtExpression ?: return
+ val right = node.valueArguments.firstOrNull()?.sourcePsi as? KtExpression ?: return
+ if (left.isFunctionType() && right.isFunctionType()) {
+ context.report(
+ ISSUE,
+ node,
+ context.getNameLocation(node),
+ BriefDescription
+ )
+ }
+ }
+ }
+ }
+
+ private fun KtExpression.isFunctionType(): Boolean = analyze(this) {
+ getKtType()?.isFunctionType == true
+ }
+
+ companion object {
+ private const val BriefDescription = "Checking lambdas for structural equality, instead " +
+ "of checking for referential equality"
+ private const val Explanation =
+ "Checking structural equality on lambdas can lead to issues, as function references " +
+ "(::lambda) do not consider their capture scope in their equals implementation. " +
+ "This means that structural equality can return true, even if the lambdas are " +
+ "different references with a different capture scope. Instead, lambdas should be" +
+ "compared referentially (=== or !==) to avoid this issue."
+
+ val ISSUE = Issue.create(
+ "LambdaStructuralEquality",
+ BriefDescription,
+ Explanation,
+ Category.CORRECTNESS, 5, Severity.ERROR,
+ Implementation(
+ LambdaStructuralEqualityDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+ }
+}
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/LambdaStructuralEqualityDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/LambdaStructuralEqualityDetectorTest.kt
new file mode 100644
index 0000000..0206fd9
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/LambdaStructuralEqualityDetectorTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+class LambdaStructuralEqualityDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = LambdaStructuralEqualityDetector()
+
+ override fun getIssues(): MutableList<Issue> = mutableListOf(
+ LambdaStructuralEqualityDetector.ISSUE
+ )
+
+ @Test
+ fun noErrors() {
+ lint().files(
+ kotlin(
+ """
+ package test
+
+ val lambda1 = { 1 }
+ val lambda2 = { 2 }
+
+ fun test() {
+ lambda1 === lambda2
+ lambda1 !== lambda2
+ }
+ """
+ )
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun errors() {
+ lint().files(
+ kotlin(
+ """
+ package test
+
+ val lambda1 = { 1 }
+ val lambda2 = { 2 }
+ val lambda3: (() -> Unit)? = null
+
+ fun test() {
+ lambda1 == lambda2
+ lambda1 != lambda2
+ lambda1.equals(lambda2)
+ lambda3?.equals(lambda2)
+ }
+ """
+ )
+ )
+ .run()
+ .expect(
+ """
+src/test/test.kt:9: Error: Checking lambdas for structural equality, instead of checking for referential equality [LambdaStructuralEquality]
+ lambda1 == lambda2
+ ~~
+src/test/test.kt:10: Error: Checking lambdas for structural equality, instead of checking for referential equality [LambdaStructuralEquality]
+ lambda1 != lambda2
+ ~~
+src/test/test.kt:11: Error: Checking lambdas for structural equality, instead of checking for referential equality [LambdaStructuralEquality]
+ lambda1.equals(lambda2)
+ ~~~~~~
+src/test/test.kt:12: Error: Checking lambdas for structural equality, instead of checking for referential equality [LambdaStructuralEquality]
+ lambda3?.equals(lambda2)
+ ~~~~~~
+4 errors, 0 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+Autofix for src/test/test.kt line 9: Change to ===:
+@@ -9 +9
+- lambda1 == lambda2
++ lambda1 === lambda2
+Autofix for src/test/test.kt line 10: Change to !==:
+@@ -10 +10
+- lambda1 != lambda2
++ lambda1 !== lambda2
+ """
+ )
+ }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt
index d90d9b3..307d512 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt
@@ -838,7 +838,7 @@
if (other !is DraggableAnchorsElement<*>) return false
if (state != other.state) return false
- if (anchors != other.anchors) return false
+ if (anchors !== other.anchors) return false
if (orientation != other.orientation) return false
return true
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt
index cf714ea..7941e5a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt
@@ -823,7 +823,7 @@
if (other !is DraggableAnchorsElement<*>) return false
if (state != other.state) return false
- if (anchors != other.anchors) return false
+ if (anchors !== other.anchors) return false
if (orientation != other.orientation) return false
return true
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
index 5cf1202..0efe678 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
@@ -155,7 +155,7 @@
previous
else null
is ComputedValueHolder ->
- if (value.compute == previous.compute)
+ if (value.compute === previous.compute)
previous
else null
else -> null
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 6e6bb2b..28e609f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -1794,7 +1794,7 @@
): ((Any) -> Unit)? {
@Suppress("NAME_SHADOWING")
val parentObserver = if (mergeReadObserver) parentObserver else null
- return if (readObserver != null && parentObserver != null && readObserver != parentObserver) {
+ return if (readObserver != null && parentObserver != null && readObserver !== parentObserver) {
{ state: Any ->
readObserver(state)
parentObserver(state)
@@ -1806,7 +1806,7 @@
writeObserver: ((Any) -> Unit)?,
parentObserver: ((Any) -> Unit)?
): ((Any) -> Unit)? =
- if (writeObserver != null && parentObserver != null && writeObserver != parentObserver) {
+ if (writeObserver != null && parentObserver != null && writeObserver !== parentObserver) {
{ state: Any ->
writeObserver(state)
parentObserver(state)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt
index d1db594b..13d2354 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt
@@ -90,7 +90,7 @@
if (autofillTypes != other.autofillTypes) return false
if (boundingBox != other.boundingBox) return false
- if (onFill != other.onFill) return false
+ if (onFill !== other.onFill) return false
return true
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt
index d6ee7f8..065c55e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt
@@ -54,7 +54,7 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is OnGloballyPositionedElement) return false
- return onGloballyPositioned == other.onGloballyPositioned
+ return onGloballyPositioned === other.onGloballyPositioned
}
override fun hashCode(): Int {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
index 91fe684..a6b2e22 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
@@ -59,7 +59,7 @@
if (this === other) return true
if (other !is OnSizeChangedModifier) return false
- return onSizeChanged == other.onSizeChanged
+ return onSizeChanged === other.onSizeChanged
}
override fun hashCode(): Int {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalConsumer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalConsumer.kt
index 6d291ff..e7bb4b1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalConsumer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalConsumer.kt
@@ -67,7 +67,7 @@
}
override fun equals(other: Any?): Boolean {
- return other is ModifierLocalConsumerImpl && other.consumer == consumer
+ return other is ModifierLocalConsumerImpl && other.consumer === consumer
}
override fun hashCode() = consumer.hashCode()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index 50dffd3..0d60bb9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -620,7 +620,7 @@
if (other !is CustomAccessibilityAction) return false
if (label != other.label) return false
- if (action != other.action) return false
+ if (action !== other.action) return false
return true
}
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
index 04f1c6d..32ac6ba 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
@@ -957,7 +957,7 @@
override fun hashCode() = constrainBlock.hashCode()
override fun equals(other: Any?) =
- constrainBlock == (other as? ConstrainAsModifier)?.constrainBlock
+ constrainBlock === (other as? ConstrainAsModifier)?.constrainBlock
}
}
@@ -1064,7 +1064,7 @@
override val layoutId: Any = ref.id
override fun equals(other: Any?) = other is ConstraintLayoutParentData &&
- ref.id == other.ref.id && constrain == other.constrain
+ ref.id == other.ref.id && constrain === other.constrain
override fun hashCode() = ref.id.hashCode() * 31 + constrain.hashCode()
}
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt
index 7584d68..29de39c 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt
@@ -236,7 +236,7 @@
if (minTransitionArea != other.minTransitionArea) return false
if (maxTransitionArea != other.maxTransitionArea) return false
if (scaleInterpolator != other.scaleInterpolator) return false
- if (viewportVerticalOffsetResolver != other.viewportVerticalOffsetResolver) return false
+ if (viewportVerticalOffsetResolver !== other.viewportVerticalOffsetResolver) return false
return true
}
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
index b3abd6e..bf11025 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
@@ -240,7 +240,7 @@
if (minTransitionArea != other.minTransitionArea) return false
if (maxTransitionArea != other.maxTransitionArea) return false
if (scaleInterpolator != other.scaleInterpolator) return false
- if (viewportVerticalOffsetResolver != other.viewportVerticalOffsetResolver) return false
+ if (viewportVerticalOffsetResolver !== other.viewportVerticalOffsetResolver) return false
return true
}