feat: parse and use Kotlin SourceDebugExtension with SMAP for rename classes and packages (PR #2389)
* feat: parse and use Kotlin SourceDebugExtension for rename classes and package * fix: fixed typo * fix: fixed spotless checks * fix: fixed spotless checks
This commit is contained in:
@@ -18,6 +18,7 @@ dependencies {
|
||||
runtimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-rename-mappings"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-kotlin-source-debug-extension"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
id("jadx-library")
|
||||
id("jadx-kotlin")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":jadx-core"))
|
||||
|
||||
testImplementation(project.project(":jadx-core").sourceSets.getByName("test").output)
|
||||
testImplementation("org.apache.commons:commons-lang3:3.17.0")
|
||||
|
||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package jadx.plugins.kotlin.smap
|
||||
|
||||
import jadx.api.plugins.options.impl.BasePluginOptionsBuilder
|
||||
import jadx.plugins.kotlin.smap.KotlinSmapPlugin.Companion.PLUGIN_ID
|
||||
|
||||
class KotlinSmapOptions : BasePluginOptionsBuilder() {
|
||||
var isClassAliasSourceDbg: Boolean = true
|
||||
private set
|
||||
|
||||
override fun registerOptions() {
|
||||
boolOption(CLASS_ALIAS_SOURCE_DBG_OPT)
|
||||
.description("rename class alias from SourceDebugExtension")
|
||||
.defaultValue(false)
|
||||
.setter { isClassAliasSourceDbg = it }
|
||||
}
|
||||
|
||||
fun isClassSourceDbg(): Boolean {
|
||||
return isClassAliasSourceDbg
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CLASS_ALIAS_SOURCE_DBG_OPT = "$PLUGIN_ID.class-alias-source-dbg"
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package jadx.plugins.kotlin.smap
|
||||
|
||||
import jadx.api.plugins.JadxPlugin
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.JadxPluginInfo
|
||||
import jadx.plugins.kotlin.smap.pass.KotlinSourceDebugExtensionPass
|
||||
|
||||
class KotlinSmapPlugin : JadxPlugin {
|
||||
|
||||
private val options = KotlinSmapOptions()
|
||||
|
||||
override fun getPluginInfo(): JadxPluginInfo {
|
||||
return JadxPluginInfo(PLUGIN_ID, "Kotlin SMAP", "Use kotlin.SourceDebugExtension annotation for rename class alias")
|
||||
}
|
||||
|
||||
override fun init(context: JadxPluginContext) {
|
||||
context.registerOptions(options)
|
||||
|
||||
if (options.isClassSourceDbg()) {
|
||||
context.addPass(KotlinSourceDebugExtensionPass(options))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PLUGIN_ID = "kotlin-smap"
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package jadx.plugins.kotlin.smap.model
|
||||
|
||||
data class ClassAliasRename(
|
||||
val pkg: String,
|
||||
val name: String,
|
||||
)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package jadx.plugins.kotlin.smap.model
|
||||
|
||||
object Constants {
|
||||
const val KOTLIN_SOURCE_DEBUG_EXTENSION = "Lkotlin/jvm/internal/SourceDebugExtension;"
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package jadx.plugins.kotlin.smap.model
|
||||
|
||||
import kotlin.math.max
|
||||
|
||||
const val KOTLIN_STRATA_NAME = "Kotlin"
|
||||
const val KOTLIN_DEBUG_STRATA_NAME = "KotlinDebug"
|
||||
|
||||
/**
|
||||
* Represents SMAP as a structure that is contained in `SourceDebugExtension` attribute of a class.
|
||||
* This structure is immutable, we can only query for a result.
|
||||
*/
|
||||
class SMAP(val fileMappings: List<FileMapping>) {
|
||||
// assuming disjoint line mappings (otherwise binary search can't be used anyway)
|
||||
private val intervals = fileMappings.flatMap { it.lineMappings }.sortedBy { it.dest }
|
||||
|
||||
fun findRange(lineNumber: Int): RangeMapping? {
|
||||
val index = intervals.binarySearch { if (lineNumber in it) 0 else it.dest - lineNumber }
|
||||
return if (index < 0) null else intervals[index]
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FILE_SECTION = "*F"
|
||||
const val LINE_SECTION = "*L"
|
||||
const val STRATA_SECTION = "*S"
|
||||
const val END = "*E"
|
||||
}
|
||||
}
|
||||
|
||||
class FileMapping(val name: String, val path: String) {
|
||||
val lineMappings = arrayListOf<RangeMapping>()
|
||||
|
||||
fun toSourceInfo(): SourceInfo =
|
||||
SourceInfo(
|
||||
name,
|
||||
path,
|
||||
lineMappings.fold(0) { result, mapping -> max(result, mapping.source + mapping.range - 1) },
|
||||
)
|
||||
|
||||
fun mapNewLineNumber(source: Int, currentIndex: Int, callSite: SourcePosition?): Int {
|
||||
// Save some space in the SMAP by reusing (or extending if it's the last one) the existing range.
|
||||
// TODO some *other* range may already cover `source`; probably too slow to check them all though.
|
||||
// Maybe keep the list ordered by `source` and use binary search to locate the closest range on the left?
|
||||
val mapping = lineMappings.lastOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSite) }
|
||||
?: lineMappings.firstOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSite) }
|
||||
?: mapNewInterval(source, currentIndex + 1, 1, callSite)
|
||||
mapping.range = max(mapping.range, source - mapping.source + 1)
|
||||
return mapping.mapSourceToDest(source)
|
||||
}
|
||||
|
||||
private fun RangeMapping.canReuseFor(newSource: Int, globalMaxDest: Int, newCallSite: SourcePosition?): Boolean =
|
||||
callSite == newCallSite && (newSource - source) in 0 until range + (if (globalMaxDest in this) 10 else 0)
|
||||
|
||||
fun mapNewInterval(source: Int, dest: Int, range: Int, callSite: SourcePosition? = null): RangeMapping =
|
||||
RangeMapping(source, dest, range, callSite, parent = this).also { lineMappings.add(it) }
|
||||
}
|
||||
|
||||
data class RangeMapping(val source: Int, val dest: Int, var range: Int, val callSite: SourcePosition?, val parent: FileMapping) {
|
||||
operator fun contains(destLine: Int): Boolean =
|
||||
dest <= destLine && destLine < dest + range
|
||||
|
||||
fun hasMappingForSource(sourceLine: Int): Boolean =
|
||||
source <= sourceLine && sourceLine < source + range
|
||||
|
||||
fun mapDestToSource(destLine: Int): SourcePosition =
|
||||
SourcePosition(source + (destLine - dest), parent.name, parent.path)
|
||||
|
||||
fun mapSourceToDest(sourceLine: Int): Int =
|
||||
dest + (sourceLine - source)
|
||||
}
|
||||
|
||||
val RangeMapping.toRange: IntRange
|
||||
get() = dest until dest + range
|
||||
|
||||
data class SourcePosition(val line: Int, val file: String, val path: String)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package jadx.plugins.kotlin.smap.model
|
||||
|
||||
data class SourceInfo(
|
||||
val sourceFileName: String?,
|
||||
val pathOrCleanFQN: String,
|
||||
val linesInFile: Int,
|
||||
)
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package jadx.plugins.kotlin.smap.pass
|
||||
|
||||
import jadx.api.plugins.pass.JadxPassInfo
|
||||
import jadx.api.plugins.pass.impl.OrderedJadxPassInfo
|
||||
import jadx.api.plugins.pass.types.JadxPreparePass
|
||||
import jadx.core.dex.attributes.AFlag
|
||||
import jadx.core.dex.nodes.RootNode
|
||||
import jadx.plugins.kotlin.smap.KotlinSmapOptions
|
||||
import jadx.plugins.kotlin.smap.utils.KotlinSmapUtils
|
||||
|
||||
class KotlinSourceDebugExtensionPass(
|
||||
private val options: KotlinSmapOptions,
|
||||
) : JadxPreparePass {
|
||||
|
||||
override fun getInfo(): JadxPassInfo {
|
||||
return OrderedJadxPassInfo(
|
||||
"SourceDebugExtensionPrepare",
|
||||
"Use kotlin.jvm.internal.SourceDebugExtension annotation to rename class & package",
|
||||
)
|
||||
.before("RenameVisitor")
|
||||
}
|
||||
|
||||
override fun init(root: RootNode) {
|
||||
if (options.isClassAliasSourceDbg) {
|
||||
for (cls in root.classes) {
|
||||
if (cls.contains(AFlag.DONT_RENAME)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// rename class & package
|
||||
val kotlinCls = KotlinSmapUtils.getClassAlias(cls)
|
||||
if (kotlinCls != null) {
|
||||
cls.rename(kotlinCls.name)
|
||||
cls.packageNode.rename(kotlinCls.pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package jadx.plugins.kotlin.smap.utils
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.plugins.kotlin.smap.model.Constants
|
||||
import jadx.plugins.kotlin.smap.model.SMAP
|
||||
|
||||
fun ClassNode.getSourceDebugExtension(): SMAP? {
|
||||
val annotation: IAnnotation? = getAnnotation(Constants.KOTLIN_SOURCE_DEBUG_EXTENSION)
|
||||
return annotation?.run {
|
||||
val smapParser = SMAPParser.parseOrNull(getParamsAsList("value")?.get(0)?.value.toString())
|
||||
return smapParser
|
||||
}
|
||||
}
|
||||
|
||||
private fun IAnnotation.getParamsAsList(paramName: String): List<EncodedValue>? {
|
||||
val encodedValue = values[paramName]
|
||||
?.takeIf { it.type == EncodedType.ENCODED_ARRAY && it.value is List<*> }
|
||||
return encodedValue?.value?.let { it as List<EncodedValue> }
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package jadx.plugins.kotlin.smap.utils
|
||||
|
||||
import jadx.core.deobf.NameMapper
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.utils.Utils
|
||||
import jadx.plugins.kotlin.smap.model.ClassAliasRename
|
||||
import jadx.plugins.kotlin.smap.model.SMAP
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import kotlin.jvm.java
|
||||
|
||||
object KotlinSmapUtils {
|
||||
|
||||
val LOG: Logger = LoggerFactory.getLogger(KotlinSmapUtils::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun getClassAlias(cls: ClassNode): ClassAliasRename? {
|
||||
val annotation = cls.getSourceDebugExtension() ?: return null
|
||||
return getClassAlias(cls, annotation)
|
||||
}
|
||||
|
||||
private fun getClassAlias(cls: ClassNode, annotation: SMAP): ClassAliasRename? {
|
||||
val firstValue = annotation.fileMappings[0].path.replace("/", ".")
|
||||
try {
|
||||
val clsName = firstValue.trim()
|
||||
.takeUnless(String::isEmpty)
|
||||
?.let(Utils::cleanObjectName)
|
||||
?: return null
|
||||
|
||||
val alias = splitAndCheckClsName(cls, clsName)
|
||||
if (alias != null) {
|
||||
RenameReasonAttr.forNode(cls).append("from SourceDebugExtension")
|
||||
return alias
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to parse SourceDebugExtension", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Don't use ClassInfo facility to not pollute class into cache
|
||||
private fun splitAndCheckClsName(originCls: ClassNode, fullClsName: String): ClassAliasRename? {
|
||||
if (!NameMapper.isValidFullIdentifier(fullClsName)) {
|
||||
return null
|
||||
}
|
||||
val pkg: String
|
||||
val name: String
|
||||
val dot = fullClsName.lastIndexOf('.')
|
||||
if (dot == -1) {
|
||||
pkg = ""
|
||||
name = fullClsName
|
||||
} else {
|
||||
pkg = fullClsName.substring(0, dot)
|
||||
name = fullClsName.substring(dot + 1)
|
||||
}
|
||||
val originClsInfo = originCls.classInfo
|
||||
val originName = originClsInfo.shortName
|
||||
if (originName == name || name.contains("$") ||
|
||||
!NameMapper.isValidIdentifier(name) || pkg.startsWith("java.")
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val newClsNode = originCls.root().resolveClass(fullClsName)
|
||||
return if (newClsNode != null) {
|
||||
// class with alias name already exist
|
||||
null
|
||||
} else {
|
||||
ClassAliasRename(pkg, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 jadx.plugins.kotlin.smap.utils
|
||||
|
||||
import jadx.plugins.kotlin.smap.model.FileMapping
|
||||
import jadx.plugins.kotlin.smap.model.KOTLIN_DEBUG_STRATA_NAME
|
||||
import jadx.plugins.kotlin.smap.model.KOTLIN_STRATA_NAME
|
||||
import jadx.plugins.kotlin.smap.model.SMAP
|
||||
|
||||
object SMAPParser {
|
||||
fun parseOrNull(mappingInfo: String): SMAP? =
|
||||
if (mappingInfo.isNotEmpty()) {
|
||||
parseStratum(mappingInfo, KOTLIN_STRATA_NAME, parseStratum(mappingInfo, KOTLIN_DEBUG_STRATA_NAME, null))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private class SMAPTokenizer(private val text: String, private val headerString: String) : Iterator<String> {
|
||||
|
||||
private var pos = 0
|
||||
private var currentLine: String? = null
|
||||
|
||||
init {
|
||||
advance()
|
||||
while (currentLine != null && currentLine != headerString) {
|
||||
advance()
|
||||
}
|
||||
if (currentLine == headerString) {
|
||||
advance()
|
||||
}
|
||||
}
|
||||
|
||||
private fun advance() {
|
||||
if (pos >= text.length) {
|
||||
currentLine = null
|
||||
return
|
||||
}
|
||||
val fromPos = pos
|
||||
while (pos < text.length && text[pos] != '\n' && text[pos] != '\r') pos++
|
||||
currentLine = text.substring(fromPos, pos)
|
||||
pos++
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return currentLine != null
|
||||
}
|
||||
|
||||
override fun next(): String {
|
||||
val res = currentLine ?: throw NoSuchElementException()
|
||||
advance()
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStratum(mappingInfo: String, stratum: String, callSites: SMAP?): SMAP? {
|
||||
val fileMappings = linkedMapOf<Int, FileMapping>()
|
||||
val iterator = SMAPTokenizer(mappingInfo, "${SMAP.STRATA_SECTION} $stratum")
|
||||
// JSR-045 allows the line section to come before the file section, but we don't generate SMAPs like this.
|
||||
if (!iterator.hasNext() || iterator.next() != SMAP.FILE_SECTION) return null
|
||||
|
||||
for (line in iterator) {
|
||||
when {
|
||||
line == SMAP.LINE_SECTION -> break
|
||||
line == SMAP.FILE_SECTION || line == SMAP.END || line.startsWith(SMAP.STRATA_SECTION) -> return null
|
||||
}
|
||||
|
||||
val indexAndFileInternalName = if (line.startsWith("+ ")) line.substring(2) else line
|
||||
val fileIndex = indexAndFileInternalName.substringBefore(' ').toInt()
|
||||
val fileName = indexAndFileInternalName.substringAfter(' ')
|
||||
val path = if (line.startsWith("+ ")) iterator.next() else fileName
|
||||
fileMappings[fileIndex] = FileMapping(fileName, path)
|
||||
}
|
||||
|
||||
for (line in iterator) {
|
||||
when {
|
||||
line == SMAP.LINE_SECTION || line == SMAP.FILE_SECTION -> return null
|
||||
line == SMAP.END || line.startsWith(SMAP.STRATA_SECTION) -> break
|
||||
}
|
||||
|
||||
// <source>#<file>,<sourceRange>:<dest>,<destMultiplier>
|
||||
val fileSeparator = line.indexOf('#')
|
||||
if (fileSeparator < 0) return null
|
||||
val destSeparator = line.indexOf(':', fileSeparator)
|
||||
if (destSeparator < 0) return null
|
||||
val sourceRangeSeparator = line.indexOf(',').let { if (it !in fileSeparator..destSeparator) destSeparator else it }
|
||||
val destMultiplierSeparator = line.indexOf(',', destSeparator).let { if (it < 0) line.length else it }
|
||||
|
||||
val file = fileMappings[line.substring(fileSeparator + 1, sourceRangeSeparator).toInt()] ?: return null
|
||||
val source = line.substring(0, fileSeparator).toInt()
|
||||
val dest = line.substring(destSeparator + 1, destMultiplierSeparator).toInt()
|
||||
val range = when {
|
||||
// These two fields have a different meaning, but for compatibility we treat them the same. See `SMAPBuilder`.
|
||||
destMultiplierSeparator != line.length -> line.substring(destMultiplierSeparator + 1).toInt()
|
||||
sourceRangeSeparator != destSeparator -> line.substring(sourceRangeSeparator + 1, destSeparator).toInt()
|
||||
else -> 1
|
||||
}
|
||||
// Here we assume that each range in `Kotlin` is entirely within at most one range in `KotlinDebug`.
|
||||
file.mapNewInterval(source, dest, range, callSites?.findRange(dest)?.let { it.mapDestToSource(it.dest) })
|
||||
}
|
||||
|
||||
return SMAP(fileMappings.values.toList())
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
jadx.plugins.kotlin.smap.KotlinSmapPlugin
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package jadx.plugins.kotlin.metadata.tests
|
||||
|
||||
import jadx.plugins.kotlin.smap.KotlinSmapOptions.Companion.CLASS_ALIAS_SOURCE_DBG_OPT
|
||||
import jadx.tests.api.SmaliTest
|
||||
import jadx.tests.api.utils.assertj.JadxAssertions.assertThat
|
||||
import jadx.tests.api.utils.assertj.JadxCodeAssertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class TestSourceDebugExtension : SmaliTest() {
|
||||
|
||||
@Test
|
||||
fun testRenameClass() {
|
||||
setupArgs {
|
||||
this[CLASS_ALIAS_SOURCE_DBG_OPT] = true
|
||||
}
|
||||
assertThatClass()
|
||||
.containsOne("androidx.compose.ui")
|
||||
.containsOne("public final class ActualKt")
|
||||
.countString(1, "reason: from SourceDebugExtension")
|
||||
}
|
||||
|
||||
private fun setupArgs(builder: MutableMap<String, Boolean>.() -> Unit = {}) {
|
||||
val allOff = mutableMapOf(
|
||||
CLASS_ALIAS_SOURCE_DBG_OPT to false,
|
||||
)
|
||||
args.pluginOptions = allOff.apply(builder).mapValues {
|
||||
if (it.value) "yes" else "no"
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertThatClass(): JadxCodeAssertions =
|
||||
assertThat(getClassNodeFromSmaliFiles("deobf", "TestKotlinSourceDebugExtension", "C6"))
|
||||
.code()
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
.class public final Ldeobf/C6;
|
||||
.super Ljava/lang/Object;
|
||||
.source "SourceFile"
|
||||
|
||||
|
||||
# annotations
|
||||
.annotation runtime Lkotlin/Metadata;
|
||||
d1 = {
|
||||
"\u0000\u000e\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u000b\n\u0000\u001a\u0018\u0010\u0001\u001a\u00020\u00032\u0006\u0010\u0001\u001a\u00020\u00002\u0006\u0010\u0002\u001a\u00020\u0000H\u0000\u00a8\u0006\u0004"
|
||||
}
|
||||
d2 = {
|
||||
"",
|
||||
"a",
|
||||
"b",
|
||||
"",
|
||||
"ui_release"
|
||||
}
|
||||
k = 0x2
|
||||
mv = {
|
||||
0x1,
|
||||
0x8,
|
||||
0x0
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.annotation build Lkotlin/jvm/internal/SourceDebugExtension;
|
||||
value = {
|
||||
"SMAP\nActual.kt\nKotlin\n*S Kotlin\n*F\n+ 1 Actual.kt\nandroidx/compose/ui/ActualKt\n+ 2 _Arrays.kt\nkotlin/collections/ArraysKt___ArraysKt\n+ 3 ListUtils.kt\nandroidx/compose/ui/util/ListUtilsKt\n*L\n1#1,50:1\n6442#2:51\n33#3,6:52\n*S KotlinDebug\n*F\n+ 1 Actual.kt\nandroidx/compose/ui/ActualKt\n*L\n35#1:51\n36#1:52,6\n*E\n"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
|
||||
# direct methods
|
||||
.method public static final a(Ljava/lang/Object;Ljava/lang/Object;)Z
|
||||
.locals 1
|
||||
|
||||
const-string v0, "a"
|
||||
|
||||
invoke-static {p0, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
|
||||
|
||||
const-string v0, "b"
|
||||
|
||||
invoke-static {p1, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
|
||||
|
||||
invoke-virtual {p0}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
|
||||
|
||||
move-result-object p0
|
||||
|
||||
invoke-virtual {p1}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
if-ne p0, p1, :cond_0
|
||||
|
||||
const/4 p0, 0x1
|
||||
|
||||
goto :goto_0
|
||||
|
||||
:cond_0
|
||||
const/4 p0, 0x0
|
||||
|
||||
:goto_0
|
||||
return p0
|
||||
.end method
|
||||
@@ -24,6 +24,7 @@ include("jadx-plugins:jadx-smali-input")
|
||||
include("jadx-plugins:jadx-java-convert")
|
||||
include("jadx-plugins:jadx-rename-mappings")
|
||||
include("jadx-plugins:jadx-kotlin-metadata")
|
||||
include("jadx-plugins:jadx-kotlin-source-debug-extension")
|
||||
include("jadx-plugins:jadx-xapk-input")
|
||||
include("jadx-plugins:jadx-aab-input")
|
||||
include("jadx-plugins:jadx-apkm-input")
|
||||
|
||||
Reference in New Issue
Block a user