Skip to content

Commit

Permalink
feat: use kotlin intrinsic methods for variables rename (#1207)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jan 19, 2022
1 parent a7c63c2 commit 86582de
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ options:
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
--deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
Expand All @@ -107,7 +108,7 @@ options:
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
Expand Down
30 changes: 28 additions & 2 deletions jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
Expand Down Expand Up @@ -101,6 +102,13 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false;

@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;

@Parameter(
names = { "--rename-flags" },
description = "fix options (comma-separated list of):"
Expand Down Expand Up @@ -130,7 +138,7 @@ public class JadxCLIArgs {

@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none",
description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
Expand Down Expand Up @@ -223,6 +231,7 @@ public JadxArgs toJadxArgs() {
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
Expand Down Expand Up @@ -326,6 +335,10 @@ public boolean isDeobfuscationParseKotlinMetadata() {
return deobfuscationParseKotlinMetadata;
}

public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}

public boolean isEscapeUnicode() {
return escapeUnicode;
}
Expand Down Expand Up @@ -412,9 +425,22 @@ public CommentsLevel convert(String value) {
}
}

public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
@Override
public UseKotlinMethodsForVarNames convert(String value) {
try {
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}

public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values)
.map(v -> v.name().toLowerCase(Locale.ROOT))
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
}
15 changes: 15 additions & 0 deletions jadx-core/src/main/java/jadx/api/JadxArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ public enum OutputFormatEnum {

private boolean useDxInput = false;

public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}

private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;

public JadxArgs() {
// use default options
}
Expand Down Expand Up @@ -433,6 +439,14 @@ public void setUseDxInput(boolean useDxInput) {
this.useDxInput = useDxInput;
}

public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}

public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}

@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
Expand All @@ -452,6 +466,7 @@ public String toString() {
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
Expand Down
4 changes: 4 additions & 0 deletions jadx-core/src/main/java/jadx/core/Jadx.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
Expand Down Expand Up @@ -131,6 +132,9 @@ public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) {
passes.add(new InlineMethods());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package jadx.core.dex.visitors.kotlin;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;

@JadxVisitor(
name = "ProcessKotlinInternals",
desc = "Use variable names from Kotlin intrinsic1 methods",
runAfter = {
InitCodeVariables.class,
DebugInfoApplyVisitor.class
},
runBefore = {
CodeRenameVisitor.class
}
)
public class ProcessKotlinInternals extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ProcessKotlinInternals.class);

private static final String KOTLIN_INTERNAL_PKG = "kotlin.jvm.internal.";
private static final String KOTLIN_INTRINSICS_CLS = KOTLIN_INTERNAL_PKG + "Intrinsics";
private static final String KOTLIN_VARNAME_SOURCE_MTH1 = "(Ljava/lang/Object;Ljava/lang/String;)V";
private static final String KOTLIN_VARNAME_SOURCE_MTH2 = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V";

private @Nullable ClassInfo kotlinIntrinsicsCls;
private Set<MethodInfo> kotlinVarNameSourceMethods;
private boolean hideInsns;

@Override
public void init(RootNode root) throws JadxException {
ClassNode kotlinCls = searchKotlinIntrinsicsClass(root);
if (kotlinCls != null) {
kotlinIntrinsicsCls = kotlinCls.getClassInfo();
kotlinVarNameSourceMethods = collectMethods(kotlinCls);
LOG.debug("Kotlin Intrinsics class: {}, methods: {}", kotlinCls, kotlinVarNameSourceMethods.size());
} else {
kotlinIntrinsicsCls = null;
LOG.debug("Kotlin Intrinsics class not found");
}
hideInsns = root.getArgs().getUseKotlinMethodsForVarNames() == UseKotlinMethodsForVarNames.APPLY_AND_HIDE;
}

@Override
public boolean visit(ClassNode cls) {
if (kotlinIntrinsicsCls == null) {
return false;
}
for (MethodNode mth : cls.getMethods()) {
processMth(mth);
}
return true;
}

private void processMth(MethodNode mth) {
if (mth.isNoCode()) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.INVOKE) {
try {
processInvoke(mth, insn);
} catch (Exception e) {
mth.addWarnComment("Failed to extract var names", e);
}
}
}
}
}

private void processInvoke(MethodNode mth, InsnNode insn) {
int argsCount = insn.getArgsCount();
if (argsCount < 2) {
return;
}
MethodInfo invokeMth = ((InvokeNode) insn).getCallMth();
if (!kotlinVarNameSourceMethods.contains(invokeMth)) {
return;
}
InsnArg firstArg = insn.getArg(0);
if (!firstArg.isRegister()) {
return;
}
RegisterArg varArg = (RegisterArg) firstArg;
boolean renamed = false;
if (argsCount == 2) {
String str = getConstString(mth, insn, 1);
if (str != null) {
renamed = checkAndRename(varArg, str);
}
} else if (argsCount == 3) {
// TODO: use second arg for rename class
String str = getConstString(mth, insn, 2);
if (str != null) {
renamed = checkAndRename(varArg, str);
}
}
if (renamed && hideInsns) {
insn.add(AFlag.DONT_GENERATE);
}
}

private boolean checkAndRename(RegisterArg arg, String str) {
String name = trimName(str);
if (NameMapper.isValidAndPrintable(name)) {
arg.getSVar().getCodeVar().setName(name);
return true;
}
return false;
}

@Nullable
private String getConstString(MethodNode mth, InsnNode insn, int arg) {
InsnArg strArg = insn.getArg(arg);
if (!strArg.isInsnWrap()) {
return null;
}
InsnNode constInsn = ((InsnWrapArg) strArg).getWrapInsn();
InsnType insnType = constInsn.getType();
if (insnType == InsnType.CONST_STR) {
return ((ConstStringNode) constInsn).getString();
}
if (insnType == InsnType.SGET) {
// revert const field inline :(
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) constInsn).getIndex();
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
if (fieldNode != null) {
String str = (String) fieldNode.get(JadxAttrType.CONSTANT_VALUE).getValue();
InsnArg newArg = InsnArg.wrapArg(new ConstStringNode(str));
insn.replaceArg(strArg, newArg);
return str;
}
}
return null;
}

private String trimName(String str) {
if (str.startsWith("$this$")) {
return str.substring(6);
}
if (str.startsWith("$")) {
return str.substring(1);
}
return str;
}

@Nullable
private static ClassNode searchKotlinIntrinsicsClass(RootNode root) {
ClassNode kotlinCls = root.resolveClass(KOTLIN_INTRINSICS_CLS);
if (kotlinCls != null) {
return kotlinCls;
}
List<ClassNode> candidates = new ArrayList<>();
for (ClassNode cls : root.getClasses()) {
if (isKotlinIntrinsicsClass(cls)) {
candidates.add(cls);
}
}
return Utils.getOne(candidates);
}

private static boolean isKotlinIntrinsicsClass(ClassNode cls) {
if (!cls.getClassInfo().getFullName().startsWith(KOTLIN_INTERNAL_PKG)) {
return false;
}
if (cls.getMethods().size() < 5) {
return false;
}
int mthCount = 0;
for (MethodNode mth : cls.getMethods()) {
if (mth.getAccessFlags().isStatic()
&& mth.getMethodInfo().getShortId().endsWith(KOTLIN_VARNAME_SOURCE_MTH1)) {
mthCount++;
}
}
return mthCount > 2;
}

private Set<MethodInfo> collectMethods(ClassNode kotlinCls) {
Set<MethodInfo> set = new HashSet<>();
for (MethodNode mth : kotlinCls.getMethods()) {
if (!mth.getAccessFlags().isStatic()) {
continue;
}
String shortId = mth.getMethodInfo().getShortId();
if (shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH1) || shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH2)) {
set.add(mth.getMethodInfo());
}
}
return set;
}
}
Loading

0 comments on commit 86582de

Please sign in to comment.