feat: inline lambdas by instance field (#1800)
This commit is contained in:
@@ -91,6 +91,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
||||
protected boolean inlineMethods = true;
|
||||
|
||||
@Parameter(names = { "--no-inline-kotlin-lambda" }, description = "disable inline for Kotlin lambdas")
|
||||
protected boolean allowInlineKotlinLambda = true;
|
||||
|
||||
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
||||
protected boolean extractFinally = true;
|
||||
|
||||
@@ -287,6 +290,7 @@ public class JadxCLIArgs {
|
||||
args.setInsertDebugLines(addDebugLines);
|
||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||
args.setInlineMethods(inlineMethods);
|
||||
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||
args.setExtractFinally(extractFinally);
|
||||
args.setRenameFlags(renameFlags);
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
@@ -368,6 +372,10 @@ public class JadxCLIArgs {
|
||||
return inlineMethods;
|
||||
}
|
||||
|
||||
public boolean isAllowInlineKotlinLambda() {
|
||||
return allowInlineKotlinLambda;
|
||||
}
|
||||
|
||||
public boolean isExtractFinally() {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public class JadxArgs {
|
||||
private boolean extractFinally = true;
|
||||
private boolean inlineAnonymousClasses = true;
|
||||
private boolean inlineMethods = true;
|
||||
private boolean allowInlineKotlinLambda = true;
|
||||
|
||||
private boolean skipResources = false;
|
||||
private boolean skipSources = false;
|
||||
@@ -263,6 +264,14 @@ public class JadxArgs {
|
||||
this.inlineMethods = inlineMethods;
|
||||
}
|
||||
|
||||
public boolean isAllowInlineKotlinLambda() {
|
||||
return allowInlineKotlinLambda;
|
||||
}
|
||||
|
||||
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||
}
|
||||
|
||||
public boolean isExtractFinally() {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
@@ -210,7 +211,31 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
protected void staticField(ICodeWriter code, FieldInfo field) throws CodegenException {
|
||||
FieldNode fieldNode = root.resolveField(field);
|
||||
if (fieldNode != null
|
||||
&& fieldNode.contains(AFlag.INLINE_INSTANCE_FIELD)
|
||||
&& fieldNode.getParentClass().contains(AType.ANONYMOUS_CLASS)) {
|
||||
FieldInitInsnAttr initInsnAttr = fieldNode.get(AType.FIELD_INIT_INSN);
|
||||
if (initInsnAttr != null) {
|
||||
InsnNode insn = initInsnAttr.getInsn();
|
||||
if (insn instanceof ConstructorInsn) {
|
||||
fieldNode.add(AFlag.DONT_GENERATE);
|
||||
inlineAnonymousConstructor(code, fieldNode.getParentClass(), (ConstructorInsn) insn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
makeStaticFieldAccess(code, field, fieldNode, mgen.getClassGen());
|
||||
}
|
||||
|
||||
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||
makeStaticFieldAccess(code, field, fieldNode, clsGen);
|
||||
}
|
||||
|
||||
private static void makeStaticFieldAccess(ICodeWriter code,
|
||||
FieldInfo field, @Nullable FieldNode fieldNode, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
// TODO
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
@@ -221,7 +246,6 @@ public class InsnGen {
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
@@ -232,10 +256,6 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
protected void staticField(ICodeWriter code, FieldInfo field) {
|
||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
||||
}
|
||||
|
||||
public void useClass(ICodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useClass(code, type);
|
||||
}
|
||||
@@ -695,9 +715,7 @@ public class InsnGen {
|
||||
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
|
||||
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
cls.ensureProcessed();
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
mth.getParentClass().addInlinedClass(cls);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -748,6 +766,7 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
cls.ensureProcessed();
|
||||
if (this.mth.getParentClass() == cls) {
|
||||
cls.remove(AType.ANONYMOUS_CLASS);
|
||||
cls.remove(AFlag.DONT_GENERATE);
|
||||
@@ -786,6 +805,8 @@ public class InsnGen {
|
||||
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
|
||||
classGen.setOuterNameGen(mgen.getNameGen());
|
||||
classGen.addClassBody(code, true);
|
||||
|
||||
mth.getParentClass().addInlinedClass(cls);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
|
||||
|
||||
@@ -270,7 +270,7 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
|
||||
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
|
||||
@@ -37,7 +37,9 @@ public enum AFlag {
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
NO_SKIP_ARGS,
|
||||
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
INLINE_INSTANCE_FIELD,
|
||||
|
||||
THIS,
|
||||
SUPER,
|
||||
|
||||
@@ -7,12 +7,19 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public class AnonymousClassAttr extends PinnedAttribute {
|
||||
|
||||
public enum InlineType {
|
||||
CONSTRUCTOR,
|
||||
INSTANCE_FIELD,
|
||||
}
|
||||
|
||||
private final ClassNode outerCls;
|
||||
private final ArgType baseType;
|
||||
private final InlineType inlineType;
|
||||
|
||||
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
|
||||
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
|
||||
this.outerCls = outerCls;
|
||||
this.baseType = baseType;
|
||||
this.inlineType = inlineType;
|
||||
}
|
||||
|
||||
public ClassNode getOuterCls() {
|
||||
@@ -23,6 +30,10 @@ public class AnonymousClassAttr extends PinnedAttribute {
|
||||
return baseType;
|
||||
}
|
||||
|
||||
public InlineType getInlineType() {
|
||||
return inlineType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AnonymousClassAttr> getAttrType() {
|
||||
return AType.ANONYMOUS_CLASS;
|
||||
@@ -30,6 +41,6 @@ public class AnonymousClassAttr extends PinnedAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
|
||||
return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -20,10 +22,21 @@ public class AccessInfo {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@MagicConstant(valuesFromClass = AccessFlags.class)
|
||||
public boolean containsFlag(int flag) {
|
||||
return (accFlags & flag) != 0;
|
||||
}
|
||||
|
||||
@MagicConstant(valuesFromClass = AccessFlags.class)
|
||||
public boolean containsFlags(int... flags) {
|
||||
for (int flag : flags) {
|
||||
if ((accFlags & flag) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public AccessInfo remove(int flag) {
|
||||
if (containsFlag(flag)) {
|
||||
return new AccessInfo(accFlags & ~flag, type);
|
||||
|
||||
@@ -384,8 +384,14 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void addFieldInitAttr(MethodNode mth, FieldNode field, InsnNode insn) {
|
||||
InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0));
|
||||
private static void addFieldInitAttr(MethodNode mth, FieldNode field, IndexInsnNode putInsn) {
|
||||
InsnNode assignInsn;
|
||||
InsnArg fldArg = putInsn.getArg(0);
|
||||
if (fldArg.isInsnWrap()) {
|
||||
assignInsn = ((InsnWrapArg) fldArg).getWrapInsn();
|
||||
} else {
|
||||
assignInsn = InsnNode.wrapArg(fldArg);
|
||||
}
|
||||
field.addAttr(new FieldInitInsnAttr(mth, assignInsn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr.InlineType;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -30,6 +32,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
UsageInfoVisitor.class
|
||||
}
|
||||
)
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public class ProcessAnonymous extends AbstractVisitor {
|
||||
|
||||
private boolean inlineAnonymousClasses;
|
||||
@@ -64,17 +67,26 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
if (!canBeAnonymous(cls)) {
|
||||
return;
|
||||
}
|
||||
MethodNode anonymousConstructor = checkUsage(cls);
|
||||
MethodNode anonymousConstructor = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
|
||||
if (anonymousConstructor == null) {
|
||||
return;
|
||||
}
|
||||
InlineType inlineType = checkUsage(cls, anonymousConstructor);
|
||||
if (inlineType == null) {
|
||||
return;
|
||||
}
|
||||
ArgType baseType = getBaseType(cls);
|
||||
if (baseType == null) {
|
||||
return;
|
||||
}
|
||||
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
||||
ClassNode outerCls;
|
||||
if (inlineType == InlineType.INSTANCE_FIELD) {
|
||||
outerCls = cls.getUseInMth().get(0).getParentClass();
|
||||
} else {
|
||||
outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
||||
}
|
||||
outerCls.addInlinedClass(cls);
|
||||
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
|
||||
cls.addAttr(new AnonymousClassAttr(outerCls, baseType, inlineType));
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
|
||||
@@ -202,14 +214,11 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
* Checks:
|
||||
* - class have only one constructor which used only once (allow common code for field init)
|
||||
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
|
||||
* - if constructor used only in class init check if possible inline by instance field
|
||||
*
|
||||
* @return anonymous constructor method
|
||||
* @return decided inline type
|
||||
*/
|
||||
private static MethodNode checkUsage(ClassNode cls) {
|
||||
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
|
||||
if (ctr == null) {
|
||||
return null;
|
||||
}
|
||||
private static InlineType checkUsage(ClassNode cls, MethodNode ctr) {
|
||||
if (ctr.getUseIn().size() != 1) {
|
||||
// check if used in common field init in all constructors
|
||||
if (!checkForCommonFieldInit(ctr)) {
|
||||
@@ -219,6 +228,9 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
MethodNode ctrUseMth = ctr.getUseIn().get(0);
|
||||
ClassNode ctrUseCls = ctrUseMth.getParentClass();
|
||||
if (ctrUseCls.equals(cls)) {
|
||||
if (checkForInstanceFieldUsage(cls, ctr)) {
|
||||
return InlineType.INSTANCE_FIELD;
|
||||
}
|
||||
// exclude self usage
|
||||
return null;
|
||||
}
|
||||
@@ -226,6 +238,20 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
// exclude usage inside inner classes
|
||||
return null;
|
||||
}
|
||||
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
|
||||
return null;
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
for (MethodNode useMth : field.getUseIn()) {
|
||||
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return InlineType.CONSTRUCTOR;
|
||||
}
|
||||
|
||||
private static boolean checkMethodsUsage(ClassNode cls, MethodNode ctr, MethodNode ctrUseMth) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth == ctr) {
|
||||
continue;
|
||||
@@ -235,18 +261,46 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
continue;
|
||||
}
|
||||
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkForInstanceFieldUsage(ClassNode cls, MethodNode ctr) {
|
||||
MethodNode ctrUseMth = ctr.getUseIn().get(0);
|
||||
if (!ctrUseMth.getMethodInfo().isClassInit()) {
|
||||
return false;
|
||||
}
|
||||
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
|
||||
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
|
||||
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
|
||||
if (instFld == null) {
|
||||
return false;
|
||||
}
|
||||
List<MethodNode> instFldUseIn = instFld.getUseIn();
|
||||
if (instFldUseIn.size() != 2
|
||||
|| !instFldUseIn.contains(ctrUseMth) // initialized in class init
|
||||
|| !instFldUseIn.containsAll(cls.getUseInMth()) // class used only with this field
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
|
||||
return false;
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field == instFld) {
|
||||
continue;
|
||||
}
|
||||
for (MethodNode useMth : field.getUseIn()) {
|
||||
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctr;
|
||||
instFld.add(AFlag.INLINE_INSTANCE_FIELD);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
|
||||
@@ -297,6 +351,13 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
|
||||
return superCls;
|
||||
}
|
||||
if (cls.root().getArgs().isAllowInlineKotlinLambda()) {
|
||||
if (superCls.getObject().equals("kotlin.jvm.internal.Lambda")) {
|
||||
// Inline such class with have different semantic: missing 'arity' property.
|
||||
// For now, it is unclear how it may affect code execution.
|
||||
return interfaceType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -325,7 +325,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
|
||||
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
|
||||
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
|
||||
if (baseTypeAttr != null) {
|
||||
if (baseTypeAttr != null && baseTypeAttr.getInlineType() == AnonymousClassAttr.InlineType.CONSTRUCTOR) {
|
||||
return baseTypeAttr.getBaseType();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ package jadx.tests.integration.inline;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
@@ -19,14 +20,17 @@ public class TestInstanceLambda extends SmaliTest {
|
||||
public static class TestCls {
|
||||
|
||||
public <T> Map<T, T> test(List<? extends T> list) {
|
||||
return toMap(list, Lambda.INSTANCE);
|
||||
return toMap(list, Lambda$1.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smali test missing 'T' definition in 'Lambda<T>'
|
||||
* Note: use '$1' so class looks like generated by compiler and pass check in
|
||||
* {@link ProcessAnonymous#canBeAnonymous(ClassNode)}
|
||||
*/
|
||||
private static class Lambda<T> implements Function<T, T> {
|
||||
public static final Lambda INSTANCE = new Lambda();
|
||||
@SuppressWarnings({ "CheckStyle", "checkstyle:TypeName" })
|
||||
private static class Lambda$1<T> implements Function<T, T> {
|
||||
public static final Lambda$1 INSTANCE = new Lambda$1();
|
||||
|
||||
@Override
|
||||
public T apply(T t) {
|
||||
@@ -35,12 +39,13 @@ public class TestInstanceLambda extends SmaliTest {
|
||||
}
|
||||
|
||||
private static <T> Map<T, T> toMap(List<? extends T> list, Function<T, T> valueMap) {
|
||||
return list.stream().collect(Collectors.toMap(k -> k, valueMap));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
useJavaInput();
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code();
|
||||
@@ -50,23 +55,23 @@ public class TestInstanceLambda extends SmaliTest {
|
||||
public void testSmaliDisableInline() {
|
||||
args.setInlineAnonymousClasses(false);
|
||||
List<ClassNode> classNodes = loadFromSmaliFiles();
|
||||
assertThat(searchTestCls(classNodes, "Lambda"))
|
||||
assertThat(searchTestCls(classNodes, "Lambda$1"))
|
||||
.code()
|
||||
.containsOne("class Lambda<T> implements Function<T, T> {");
|
||||
.containsOne("class Lambda$1<T> implements Function<T, T> {");
|
||||
assertThat(searchTestCls(classNodes, "TestCls"))
|
||||
.code()
|
||||
.containsOne("Lambda.INSTANCE");
|
||||
.containsOne("Lambda$1.INSTANCE");
|
||||
}
|
||||
|
||||
@NotYetImplemented("Inline lambda by instance field")
|
||||
@Test
|
||||
public void testSmali() {
|
||||
List<ClassNode> classNodes = loadFromSmaliFiles();
|
||||
assertThat(classNodes)
|
||||
assertThat(ListUtils.filter(classNodes, c -> !c.contains(AFlag.DONT_GENERATE)))
|
||||
.describedAs("Expect lambda to be inlined")
|
||||
.hasSize(1);
|
||||
assertThat(searchTestCls(classNodes, "TestCls"))
|
||||
.code()
|
||||
.doesNotContain("Lambda.INSTANCE");
|
||||
.doesNotContain("Lambda$1.INSTANCE")
|
||||
.containsOne("toMap(list, new Function<T, T>() {");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.class public Linline/Lambda;
|
||||
.class public Linline/Lambda$1;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.implements Ljava/util/function/Function;
|
||||
@@ -12,13 +12,13 @@
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.field public static final INSTANCE:Linline/Lambda;
|
||||
.field public static final INSTANCE:Linline/Lambda$1;
|
||||
|
||||
.method static constructor <clinit>()V
|
||||
.registers 1
|
||||
new-instance v0, Linline/Lambda;
|
||||
invoke-direct {v0}, Linline/Lambda;-><init>()V
|
||||
sput-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
|
||||
new-instance v0, Linline/Lambda$1;
|
||||
invoke-direct {v0}, Linline/Lambda$1;-><init>()V
|
||||
sput-object v0, Linline/Lambda$1;->INSTANCE:Linline/Lambda$1;
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
@@ -15,18 +15,12 @@
|
||||
}
|
||||
.end annotation
|
||||
|
||||
sget-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
|
||||
sget-object v0, Linline/Lambda$1;->INSTANCE:Linline/Lambda$1;
|
||||
invoke-static {p1, v0}, Linline/TestCls;->toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
|
||||
.method private static synthetic lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
.registers 1
|
||||
return-object p0
|
||||
.end method
|
||||
|
||||
.method private static toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
|
||||
.registers 4
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
@@ -43,14 +37,6 @@
|
||||
}
|
||||
.end annotation
|
||||
|
||||
invoke-interface {p0}, Ljava/util/List;->stream()Ljava/util/stream/Stream;
|
||||
move-result-object v0
|
||||
invoke-custom {}, call_site_0("apply", ()Ljava/util/function/Function;, (Ljava/lang/Object;)Ljava/lang/Object;, invoke-static@Linline/TestCls;->lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/Object;)Ljava/lang/Object;)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
|
||||
move-result-object v1
|
||||
invoke-static {v1, p1}, Ljava/util/stream/Collectors;->toMap(Ljava/util/function/Function;Ljava/util/function/Function;)Ljava/util/stream/Collector;
|
||||
move-result-object v1
|
||||
invoke-interface {v0, v1}, Ljava/util/stream/Stream;->collect(Ljava/util/stream/Collector;)Ljava/lang/Object;
|
||||
move-result-object v0
|
||||
check-cast v0, Ljava/util/Map;
|
||||
const/4 v0, 0x0
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
@@ -401,6 +401,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.inlineMethods = inlineMethods;
|
||||
}
|
||||
|
||||
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||
}
|
||||
|
||||
public void setExtractFinally(boolean extractFinally) {
|
||||
this.extractFinally = extractFinally;
|
||||
}
|
||||
|
||||
@@ -538,6 +538,13 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox inlineKotlinLambdas = new JCheckBox();
|
||||
inlineKotlinLambdas.setSelected(settings.isAllowInlineKotlinLambda());
|
||||
inlineKotlinLambdas.addItemListener(e -> {
|
||||
settings.setAllowInlineKotlinLambda(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox extractFinally = new JCheckBox();
|
||||
extractFinally.setSelected(settings.isExtractFinally());
|
||||
extractFinally.addItemListener(e -> {
|
||||
@@ -581,6 +588,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
other.addRow(NLS.str("preferences.useDebugInfo"), useDebugInfo);
|
||||
other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous);
|
||||
other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods);
|
||||
other.addRow(NLS.str("preferences.inlineKotlinLambdas"), inlineKotlinLambdas);
|
||||
other.addRow(NLS.str("preferences.extractFinally"), extractFinally);
|
||||
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
|
||||
other.addRow(NLS.str("preferences.useDx"), useDx);
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=Import statements generieren
|
||||
preferences.useDebugInfo=Debug-Infos verwenden
|
||||
preferences.inlineAnonymous=Anonyme Inline-Klassen
|
||||
preferences.inlineMethods=Inline-Methoden
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
#preferences.extractFinally=Extract finally block
|
||||
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
|
||||
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=Use import statements
|
||||
preferences.useDebugInfo=Use debug info
|
||||
preferences.inlineAnonymous=Inline anonymous classes
|
||||
preferences.inlineMethods=Inline methods
|
||||
preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
preferences.extractFinally=Extract finally block
|
||||
preferences.fsCaseSensitive=File system is case-sensitive
|
||||
preferences.skipResourcesDecode=Don't decode resources
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.replaceConsts=Reemplazar constantes
|
||||
#preferences.useDebugInfo=Use debug info
|
||||
#preferences.inlineAnonymous=
|
||||
#preferences.inlineMethods=Inline methods
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
#preferences.extractFinally=Extract finally block
|
||||
#preferences.fsCaseSensitive=
|
||||
preferences.skipResourcesDecode=No descodificar recursos
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=import 문 사용
|
||||
preferences.useDebugInfo=디버그 정보 사용
|
||||
preferences.inlineAnonymous=인라인 익명 클래스
|
||||
preferences.inlineMethods=인라인 메서드
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
preferences.extractFinally=finally 블록 추출
|
||||
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
|
||||
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=Utilizar declaração de imports
|
||||
preferences.useDebugInfo=Utilizar informação de depuração
|
||||
preferences.inlineAnonymous=Classes anônimas de uma linha
|
||||
preferences.inlineMethods=Métodos de uma linha
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
preferences.extractFinally=Extrair blocos finally
|
||||
preferences.fsCaseSensitive=Sistema de arquivo diferencia maiúsculas de minúsculas
|
||||
preferences.skipResourcesDecode=Não decodificar recursos
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=Использовать импорты
|
||||
preferences.useDebugInfo=Отладочная информация
|
||||
preferences.inlineAnonymous=Объединять анонимные классы
|
||||
preferences.inlineMethods=Объединять методы
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
preferences.extractFinally=Вычленять finally блоки
|
||||
preferences.fsCaseSensitive=Учитывать регистр в файловой системе
|
||||
preferences.skipResourcesDecode=Не декодировать ресурсы
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=使用 import 语句
|
||||
preferences.useDebugInfo=启用调试信息
|
||||
preferences.inlineAnonymous=内联匿名类
|
||||
preferences.inlineMethods=内联方法
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
preferences.extractFinally=提取finally块
|
||||
preferences.fsCaseSensitive=文件系统区分大小写
|
||||
preferences.skipResourcesDecode=不反编译资源文件
|
||||
|
||||
@@ -158,6 +158,7 @@ preferences.useImports=使用 import 陳述式
|
||||
preferences.useDebugInfo=使用除錯資訊
|
||||
preferences.inlineAnonymous=內嵌匿名類別
|
||||
preferences.inlineMethods=內嵌方式
|
||||
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
|
||||
preferences.extractFinally=擷取 finally 區塊
|
||||
preferences.fsCaseSensitive=檔案系統區分大小寫
|
||||
preferences.skipResourcesDecode=不要為資源解碼
|
||||
|
||||
Reference in New Issue
Block a user