Skip to content

Commit

Permalink
Introduce ComposableCallstackTracker (#81)
Browse files Browse the repository at this point in the history
This feature is experimental. Resolves #77.
  • Loading branch information
jisungbin authored Jan 19, 2024
1 parent 4358ec0 commit ce95b8b
Show file tree
Hide file tree
Showing 67 changed files with 1,198 additions and 676 deletions.
5 changes: 3 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
ktlint_standard_filename = disabled
ktlint_standard_annotation = disabled
ktlint_standard_wrapping = disabled
ktlint_standard_package-name = disabled
ktlint_standard_import-ordering = disabled
ktlint_standard_max-line-length = disabled
ktlint_standard_annotation = disabled
ktlint_standard_wrapping = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_parameter-list-wrapping = disabled
ktlint_standard_multiline-if-else = disabled
Expand Down
17 changes: 17 additions & 0 deletions compiler-base/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
kotlin("jvm")
id(libs.plugins.gradle.publish.maven.get().pluginId)
}

sourceSets {
getByName("main").java.srcDir("src/main/kotlin")
}

kotlin {
explicitApi()
}

dependencies {
implementation(libs.kotlin.compiler.embedded)
implementation(libs.fastlist)
}
4 changes: 4 additions & 0 deletions compiler-base/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_NAME=ComposeInvestigator Compiler Base
POM_ARTIFACT_ID=composeinvestigator-compiler-base

VERSION_NAME=0.1.0-SNAPSHOT
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

package land.sungbin.composeinvestigator.compiler.util
package land.sungbin.composeinvestigator.compiler.base

internal class HandledMap {
private val map = mutableMapOf<String, Unit>()
public class HandledMap {
private val map = mutableMapOf<Int, Unit>()

fun handle(vararg key: Any): Boolean {
val finalKey = key.joinToString(separator = "$", transform = Any::toString)
public fun handle(vararg key: Any): Boolean {
val finalKey = key.contentHashCode()
return if (map.containsKey(finalKey)) {
false
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

package land.sungbin.composeinvestigator.compiler.util
package land.sungbin.composeinvestigator.compiler.base

import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl

internal fun IrPluginContext.irString(
public fun IrPluginContext.irString(
value: String,
startOffset: Int = UNDEFINED_OFFSET,
endOffset: Int = UNDEFINED_OFFSET,
Expand All @@ -23,18 +23,24 @@ internal fun IrPluginContext.irString(
value = value,
)

internal fun IrPluginContext.irInt(value: Int): IrConst<Int> =
IrConstImpl.int(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
type = irBuiltIns.intType,
value = value,
)
public fun IrPluginContext.irInt(
value: Int,
startOffset: Int = UNDEFINED_OFFSET,
endOffset: Int = UNDEFINED_OFFSET,
): IrConst<Int> = IrConstImpl.int(
startOffset = startOffset,
endOffset = endOffset,
type = irBuiltIns.intType,
value = value,
)

internal fun IrPluginContext.irBoolean(value: Boolean): IrConst<Boolean> =
IrConstImpl.boolean(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
type = irBuiltIns.booleanType,
value = value,
)
public fun IrPluginContext.irBoolean(
value: Boolean,
startOffset: Int = UNDEFINED_OFFSET,
endOffset: Int = UNDEFINED_OFFSET,
): IrConst<Boolean> = IrConstImpl.boolean(
startOffset = startOffset,
endOffset = endOffset,
type = irBuiltIns.booleanType,
value = value,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

package land.sungbin.composeinvestigator.compiler.util
package land.sungbin.composeinvestigator.compiler.base

import land.sungbin.composeinvestigator.compiler.internal.UNKNOWN_STRING
import org.jetbrains.kotlin.backend.wasm.ir2wasm.getSourceLocation
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrFunction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

package land.sungbin.composeinvestigator.compiler.util
package land.sungbin.composeinvestigator.compiler.base

import land.sungbin.fastlist.fastForEach
import org.jetbrains.kotlin.ir.IrElementBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,35 @@
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

@file:Suppress("unused", "MemberVisibilityCanBePrivate")

package land.sungbin.composeinvestigator.compiler.util
package land.sungbin.composeinvestigator.compiler.base

import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.CompilerConfiguration

internal class VerboseLogger(configuration: CompilerConfiguration) {
public class VerboseLogger(configuration: CompilerConfiguration) {
private val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
private var verbose = false

fun verbose() {
public fun verbose() {
verbose = true
}

fun warn(message: Any?, location: CompilerMessageSourceLocation?) {
public fun warn(message: Any?, location: CompilerMessageSourceLocation?) {
if (verbose) {
messageCollector.report(CompilerMessageSeverity.WARNING, message.toString(), location)
}
}

fun error(message: Any?, location: CompilerMessageSourceLocation?) {
public fun error(message: Any?, location: CompilerMessageSourceLocation?) {
if (verbose) {
messageCollector.report(CompilerMessageSeverity.ERROR, message.toString(), location)
}
}

operator fun invoke(value: Any?, location: CompilerMessageSourceLocation? = null) {
public operator fun invoke(value: Any?, location: CompilerMessageSourceLocation? = null) {
warn(message = value, location = location)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Designed and developed by Ji Sungbin 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

package land.sungbin.composeinvestigator.compiler.base

import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames

// ===== PACKAGE ===== //

public const val AndroidxComposeRuntime: String = "androidx.compose.runtime"
public const val ComposeInvestigatorRuntime: String = "land.sungbin.composeinvestigator.runtime"
public const val ComposeInvestigatorRuntimeAffect: String = "land.sungbin.composeinvestigator.runtime.affect"

// ===== FULLY-QUALIFIED NAME ===== //

// START Kotlin/Java Standard Library
public val ITERABLE_TO_LIST_FQN: FqName = FqName("kotlin.collections.toList")
public val EMPTY_LIST_FQN: FqName = FqName("kotlin.collections.emptyList")

public val MUTABLE_LIST_OF_FQN: FqName = FqName("kotlin.collections.mutableListOf")
public val MUTABLE_LIST_ADD_FQN: FqName = FqName("kotlin.collections.MutableList.add")

public val HASH_CODE_FQN: FqName = FqName("kotlin.hashCode")

public val STACK_FQN: FqName = FqName("java.util.Stack")
public val Stack_PUSH: Name = Name.identifier("push")
public val Stack_POP: Name = Name.identifier("pop")
// END Kotlin/Java Standard Library

// START Compose Runtime
public val COMPOSER_FQN: FqName = FqName("$AndroidxComposeRuntime.Composer")

public val Composer_SKIPPING: Name = Name.identifier("skipping")
public val Composer_SKIP_TO_GROUP_END: Name = Name.identifier("skipToGroupEnd")

public val SCOPE_UPDATE_SCOPE_FQN: FqName = FqName("$AndroidxComposeRuntime.ScopeUpdateScope")
public val ScopeUpdateScope_UPDATE_SCOPE: Name = Name.identifier("updateScope")

public val STATE_FQN: FqName = FqName("$AndroidxComposeRuntime.State")
// END Compose Runtime

// START Compose Animation
public val ANIMATABLE_FQN: FqName = FqName("androidx.compose.animation.core.Animatable")
// END Compose Animation

// START ComposeInvestigatorConfig
public val COMPOSE_INVESTIGATOR_CONFIG_FQN: FqName = FqName("$ComposeInvestigatorRuntime.ComposeInvestigatorConfig")
public val ComposeInvestigatorConfig_INVALIDATION_LOGGER: Name = Name.identifier("invalidationLogger")
// END ComposeInvestigatorConfig

// START ComposableInvalidationLogger
public val COMPOSABLE_INVALIDATION_LOGGER_FQN: FqName = FqName("$ComposeInvestigatorRuntime.ComposableInvalidationLogger")
public val ComposableInvalidationLogger_INVOKE: Name = Name.identifier("invoke")

public val INVALIDATION_REASON_FQN: FqName = FqName("$ComposeInvestigatorRuntime.InvalidationReason")
public val InvalidationReason_Invalidate: Name = Name.identifier("Invalidate")

public val COMPOSABLE_INVALIDATION_TYPE_FQN: FqName = FqName("$ComposeInvestigatorRuntime.ComposableInvalidationType")
public val ComposableInvalidationType_PROCESSED: Name = Name.identifier("Processed")
public val ComposableInvalidationType_SKIPPED: Name = Name.identifier("Skipped")
// END ComposableInvalidationLogger

// START ComposableInvalidationTrackTable
public val CURRENT_COMPOSABLE_INVALIDATION_TRACKER_FQN: FqName = FqName("$ComposeInvestigatorRuntime.currentComposableInvalidationTracker")

public val COMPOSABLE_NAME_FQN: FqName = FqName("$ComposeInvestigatorRuntime.ComposableName")

public val COMPOSABLE_INVALIDATION_TRACK_TABLE_FQN: FqName = FqName("$ComposeInvestigatorRuntime.ComposableInvalidationTrackTable")
public val ComposableInvalidationTrackTable_CURRENT_COMPOSABLE_NAME: Name = Name.identifier("currentComposableName")
public val ComposableInvalidationTrackTable_CURRENT_COMPOSABLE_KEY_NAME: Name = Name.identifier("currentComposableKeyName")
public val ComposableInvalidationTrackTable_CALL_LISTENERS: Name = Name.identifier("callListeners")
public val ComposableInvalidationTrackTable_COMPUTE_INVALIDATION_REASON: Name = Name.identifier("computeInvalidationReason")
// END ComposableInvalidationTrackTable

// START StateObjectTracker
public val REGISTER_STATE_OBJECT_TRACKING_FQN: FqName = FqName("$ComposeInvestigatorRuntime.registerStateObjectTracking")
// END StateObjectTracker

// START DeclarationStability
public val DECLARATION_STABILITY_FQN: FqName = FqName("$ComposeInvestigatorRuntime.DeclarationStability")
public val DeclarationStability_CERTAIN: Name = Name.identifier("Certain")
public val DeclarationStability_RUNTIME: Name = Name.identifier("Runtime")
public val DeclarationStability_UNKNOWN: Name = Name.identifier("Unknown")
public val DeclarationStability_PARAMETER: Name = Name.identifier("Parameter")
public val DeclarationStability_COMBINED: Name = Name.identifier("Combined")
// END DeclarationStability

// START affect/AffectedField
public val AFFECTED_FIELD_FQN: FqName = FqName("$ComposeInvestigatorRuntimeAffect.AffectedField")
public val AffectedField_VALUE_PARAMETER: Name = Name.identifier("ValueParameter")
// END affect/AffectableField

// START affect/AffectedComposable
public val AFFECTED_COMPOSABLE_FQN: FqName = FqName("$ComposeInvestigatorRuntimeAffect.AffectedComposable")
// END affect/AffectedComposable

public fun CallableId.Companion.fromFqName(fqName: FqName): CallableId =
CallableId(packageName = fqName.parent(), callableName = fqName.shortName())

@Suppress("UnusedReceiverParameter")
public val SpecialNames.UNKNOWN_STRING: String get() = "<unknown>"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ kotlin {
}

dependencies {
api(projects.compilerBase)

compileOnly(libs.kotlin.compiler.embedded)
implementation(libs.jetbrains.annotation)

Expand Down
4 changes: 4 additions & 0 deletions compiler-callstack-track/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_NAME=ComposeInvestigator Compiler Callstack Tracker
POM_ARTIFACT_ID=composeinvestigator-compiler-callstack

VERSION_NAME=0.1.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Designed and developed by Ji Sungbin 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

@file:Suppress("unused", "DEPRECATION", "UnstableApiUsage")

package land.sungbin.composeinvestigator.compiler.callstack

import com.google.auto.service.AutoService
import land.sungbin.composeinvestigator.compiler.base.VerboseLogger
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.extensions.LoadingOrder
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration

@AutoService(ComponentRegistrar::class)
public class ComposableCallstackTrackPluginRegistrar : ComponentRegistrar {
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
val verbose = configuration[ComposableCallstackTrackerConfiguration.KEY_VERBOSE]?.toBooleanStrictOrNull() ?: false
val logger = VerboseLogger(configuration).apply { if (verbose) verbose() }

project.extensionArea
.getExtensionPoint(IrGenerationExtension.extensionPointName)
.registerExtension(
ComposableCallstackTrackingExtension(logger = logger),
LoadingOrder.FIRST,
project,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@

@file:Suppress("unused")

package land.sungbin.composeinvestigator.compiler
package land.sungbin.composeinvestigator.compiler.callstack

import com.google.auto.service.AutoService
import land.sungbin.composeinvestigator.compiler.ComposeInvestigatorConfiguration.KEY_VERBOSE
import land.sungbin.composeinvestigator.compiler.callstack.ComposableCallstackTrackerConfiguration.KEY_VERBOSE
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey

public object ComposeInvestigatorConfiguration {
public object ComposableCallstackTrackerConfiguration {
public val KEY_VERBOSE: CompilerConfigurationKey<String> = CompilerConfigurationKey<String>("Whether to enable verbose log")
}

@AutoService(CommandLineProcessor::class)
public class ComposeInvestigatorCommandLineProcessor : CommandLineProcessor {
public class ComposableCallstackTrackerCommandLineProcessor : CommandLineProcessor {
override val pluginId: String = PLUGIN_ID
override val pluginOptions: List<CliOption> = listOf(OPTION_VERBOSE)

Expand All @@ -35,7 +35,7 @@ public class ComposeInvestigatorCommandLineProcessor : CommandLineProcessor {
}

public companion object {
public const val PLUGIN_ID: String = "land.sungbin.composeinvestigator.compiler"
public const val PLUGIN_ID: String = "land.sungbin.composeinvestigator.compiler.callstack"

public val OPTION_VERBOSE: CliOption = CliOption(
optionName = "verbose",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Designed and developed by Ji Sungbin 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE
*/

package land.sungbin.composeinvestigator.compiler.callstack

import land.sungbin.composeinvestigator.compiler.base.VerboseLogger
import land.sungbin.composeinvestigator.compiler.callstack.internal.ComposableCallstackTrackingTransformer
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid

internal class ComposableCallstackTrackingExtension(private val logger: VerboseLogger) : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
moduleFragment.transformChildrenVoid(
ComposableCallstackTrackingTransformer(
context = pluginContext,
logger = logger,
),
)
}
}
Loading

0 comments on commit ce95b8b

Please sign in to comment.