fix: improve anonymous class inline (#523)

This commit is contained in:
Skylot
2021-12-26 13:06:49 +00:00
parent 5de46b7e40
commit c7795bfc48
14 changed files with 583 additions and 180 deletions
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
@@ -135,6 +136,7 @@ public class Jadx {
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
@@ -728,6 +728,19 @@ public class InsnGen {
code.add("new ");
useClass(code, parent);
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
@@ -33,6 +33,7 @@ public enum AFlag {
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
@@ -47,7 +47,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -283,12 +282,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return true;
}
public boolean checkProcessed() {
return getTopParentClass().getState().isProcessComplete();
}
public void ensureProcessed() {
ClassNode topClass = getTopParentClass();
ProcessState state = topClass.getState();
if (state != PROCESS_COMPLETE) {
if (!checkProcessed()) {
ClassNode topParentClass = getTopParentClass();
throw new JadxRuntimeException("Expected class to be processed at this point,"
+ " class: " + topClass + ", state: " + state);
+ " class: " + topParentClass + ", state: " + topParentClass.getState());
}
}
@@ -0,0 +1,132 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
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.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "AnonymousClassVisitor",
desc = "Prepare anonymous class for inline",
runBefore = {
ModVisitor.class,
CodeShrinkVisitor.class
}
)
public class AnonymousClassVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
processAnonymousConstructor(mth);
break;
}
}
}
return true;
}
private static void processAnonymousConstructor(MethodNode mth) {
List<InsnNode> usedInsns = new ArrayList<>();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
if (argsMap.isEmpty()) {
mth.add(AFlag.NO_SKIP_ARGS);
} else {
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
}
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
}
@@ -1,7 +1,10 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags;
@@ -55,7 +58,7 @@ public class ClassModifier extends AbstractVisitor {
removeSyntheticFields(cls);
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
cls.getMethods().forEach(ClassModifier::cleanInsnsInAnonymousConstructor);
cls.getMethods().forEach(ClassModifier::processAnonymousConstructor);
return false;
}
@@ -326,27 +329,86 @@ public class ClassModifier extends AbstractVisitor {
/**
* Remove super call and put into removed fields from anonymous constructor
*/
private static void cleanInsnsInAnonymousConstructor(MethodNode mth) {
private static void processAnonymousConstructor(MethodNode mth) {
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
InsnType type = insn.getType();
if (type == InsnType.CONSTRUCTOR) {
ConstructorInsn ctorInsn = (ConstructorInsn) insn;
if (ctorInsn.isSuper()) {
ctorInsn.add(AFlag.DONT_GENERATE);
}
} else if (type == InsnType.IPUT) {
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode fieldNode = mth.root().resolveField(fldInfo);
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) {
insn.add(AFlag.DONT_GENERATE);
}
}
List<InsnNode> usedInsns = new ArrayList<>();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
@@ -1,7 +1,5 @@
package jadx.core.dex.visitors;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -19,10 +17,9 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
@@ -46,6 +43,7 @@ import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition;
@@ -432,96 +430,39 @@ public class ModVisitor extends AbstractVisitor {
return false;
}
/**
* For args in anonymous constructor invoke apply:
* - forbid inline into constructor call
* - make variables final (compiler require this implicitly)
*/
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
MethodInfo callMth = co.getCallMth();
MethodNode callMthNode = mth.root().resolveMethod(callMth);
if (callMthNode == null) {
IMethodDetails callMthDetails = mth.root().getMethodUtils().getMethodDetails(co);
if (!(callMthDetails instanceof MethodNode)) {
return;
}
ClassNode classNode = callMthNode.getParentClass();
if (!classNode.isAnonymous()) {
MethodNode callMth = (MethodNode) callMthDetails;
if (!callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR) || callMth.contains(AFlag.NO_SKIP_ARGS)) {
return;
}
if (!mth.getParentClass().getInnerClasses().contains(classNode)) {
return;
}
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
if (argsMap.isEmpty() && !callMthNode.getArgRegs().isEmpty()) {
return;
}
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
SSAVar sVar = reg.getSVar();
if (sVar != null) {
sVar.getCodeVar().setFinal(true);
SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
if (attr != null) {
int argsCount = Math.min(callMth.getArgRegs().size(), co.getArgsCount());
for (int i = 0; i < argsCount; i++) {
if (attr.isSkip(i)) {
anonymousCallArgMod(co.getArg(i));
}
reg.add(AFlag.DONT_INLINE);
reg.add(AFlag.SKIP_ARG);
}
} else {
// additional info not available apply mods to all args (the safest solution)
co.getArguments().forEach(ModVisitor::anonymousCallArgMod);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) {
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
MethodInfo callMth = callMthNode.getMethodInfo();
ClassNode cls = callMthNode.getParentClass();
ClassNode parentClass = cls.getParentClass();
List<RegisterArg> argList = callMthNode.getArgRegs();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType())) {
startArg = 1;
private static void anonymousCallArgMod(InsnArg arg) {
arg.add(AFlag.DONT_INLINE);
if (arg.isRegister()) {
((RegisterArg) arg).getSVar().getCodeVar().setFinal(true);
}
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
FieldNode fieldNode = null;
if (useInsn.getType() == InsnType.IPUT) {
FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex();
fieldNode = cls.searchField(field);
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
} else if (useInsn.getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
} else {
return Collections.emptyMap();
}
map.put(co.getArg(i), fieldNode);
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
/**
@@ -1,13 +1,19 @@
package jadx.core.dex.visitors;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -36,89 +42,129 @@ public class ProcessAnonymous extends AbstractVisitor {
}
private static void markAnonymousClass(ClassNode cls) {
if (usedOnlyOnce(cls) || isAnonymous(cls) || isLambdaCls(cls)) {
if (isStaticFieldUsedOutside(cls)) {
return;
}
cls.add(AFlag.ANONYMOUS_CLASS);
cls.addAttr(new AnonymousClassBaseAttr(getBaseType(cls)));
cls.add(AFlag.DONT_GENERATE);
boolean synthetic = cls.getAccessFlags().isSynthetic()
|| cls.getClassInfo().getShortName().contains("$")
|| Character.isDigit(cls.getClassInfo().getShortName().charAt(0));
if (!synthetic) {
return;
}
MethodNode anonymousConstructor = checkUsage(cls);
if (anonymousConstructor == null) {
return;
}
ArgType baseType = getBaseType(cls);
if (baseType == null) {
return;
}
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
cls.add(AFlag.ANONYMOUS_CLASS);
cls.addAttr(new AnonymousClassBaseAttr(baseType));
cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
// force anonymous class to be processed before outer class,
// actual usage of outer class will be removed at anonymous class process,
// see ModVisitor.processAnonymousConstructor method
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
ListUtils.safeRemove(cls.getDependencies(), outerCls);
ListUtils.safeRemove(outerCls.getUseIn(), cls);
}
/**
* 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)
*
* @return anonymous constructor method
*/
private static MethodNode checkUsage(ClassNode cls) {
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (ctr == null) {
return null;
}
if (ctr.getUseIn().size() != 1) {
// check if used in common field init in all constructors
if (!checkForCommonFieldInit(ctr)) {
return null;
}
}
MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) {
// exclude self usage
return null;
}
for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) {
continue;
}
for (MethodNode useMth : mth.getUseIn()) {
if (useMth.equals(ctrUseMth)) {
continue;
}
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
return null;
}
}
}
for (FieldNode field : cls.getFields()) {
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null;
}
}
}
return ctr;
}
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
ClassNode useCls = useMth.getParentClass();
if (useCls.equals(cls)) {
return false;
}
if (accessFlags.isSynthetic()) {
// allow synthetic usage in inner class
return !useCls.getParentClass().equals(cls);
}
return true;
}
/**
* Checks:
* + all in constructors
* + all usage in one class
* - same field put (ignored: methods not loaded yet)
*/
private static boolean checkForCommonFieldInit(MethodNode ctrMth) {
List<MethodNode> ctrUse = ctrMth.getUseIn();
if (ctrUse.isEmpty()) {
return false;
}
ClassNode firstUseCls = ctrUse.get(0).getParentClass();
return ListUtils.allMatch(ctrUse, m -> m.isConstructor() && m.getParentClass().equals(firstUseCls));
}
@Nullable
private static ArgType getBaseType(ClassNode cls) {
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
int interfacesCount = cls.getInterfaces().size();
if (interfacesCount > 1) {
return null;
}
return parent != null ? parent : ArgType.OBJECT;
}
private static boolean isStaticFieldUsedOutside(ClassNode cls) {
ClassNode topCls = cls.getTopParentClass();
for (FieldNode field : cls.getFields()) {
if (field.isStatic()) {
for (MethodNode useMth : field.getUseIn()) {
ClassNode useCls = useMth.getParentClass().getTopParentClass();
if (!useCls.equals(topCls)) {
return true;
}
}
ArgType superCls = cls.getSuperClass();
if (superCls == null || superCls.equals(ArgType.OBJECT)) {
if (interfacesCount == 1) {
return cls.getInterfaces().get(0);
}
return ArgType.OBJECT;
}
return false;
}
private static boolean usedOnlyOnce(ClassNode cls) {
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
// used only once
boolean synthetic = cls.getAccessFlags().isSynthetic() || cls.getClassInfo().getShortName().contains("$");
if (synthetic) {
// must have only one constructor which used only once
MethodNode ctr = null;
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
if (ctr != null) {
ctr = null;
break;
}
ctr = mth;
}
}
return ctr != null && ctr.getUseIn().size() == 1;
}
if (interfacesCount == 0) {
return superCls;
}
return false;
}
private static boolean isAnonymous(ClassNode cls) {
return cls.getClassInfo().isInner()
&& Character.isDigit(cls.getClassInfo().getShortName().charAt(0))
&& cls.getMethods().stream().filter(MethodNode::isConstructor).count() == 1;
}
private static boolean isLambdaCls(ClassNode cls) {
return cls.getAccessFlags().isSynthetic()
&& cls.getAccessFlags().isFinal()
&& cls.getClassInfo().getRawName().contains(".-$$Lambda$")
&& countStaticFields(cls) == 0;
}
private static int countStaticFields(ClassNode cls) {
int c = 0;
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isStatic()) {
c++;
}
// check if super class already implement that interface (weird case)
ArgType interfaceType = cls.getInterfaces().get(0);
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls;
}
return c;
return null;
}
}
@@ -154,7 +154,7 @@ public class SignatureProcessor extends AbstractVisitor {
return newArgTypes;
}
}
mth.addWarnComment("Incorrect args count in method signature: " + sp.getSignature());
mth.addDebugComment("Incorrect args count in method signature: " + sp.getSignature());
return null;
}
for (int i = 0; i < len; i++) {
@@ -7,6 +7,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
@@ -66,4 +67,45 @@ public class ListUtils {
list.add(newObj);
return list;
}
public static <T> void safeRemove(List<T> list, T obj) {
if (list != null && !list.isEmpty()) {
list.remove(obj);
}
}
/**
* Search exactly one element in list by filter
*
* @return null if found not exactly one element (zero or more than one)
*/
@Nullable
public static <T> T filterOnlyOne(List<T> list, Predicate<T> filter) {
if (list == null || list.isEmpty()) {
return null;
}
T found = null;
for (T element : list) {
if (filter.test(element)) {
if (found != null) {
// found second
return null;
}
found = element;
}
}
return found;
}
public static <T> boolean allMatch(List<T> list, Predicate<T> test) {
if (list == null || list.isEmpty()) {
return false;
}
for (T element : list) {
if (!test.test(element)) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,41 @@
package jadx.tests.integration.inner;
import org.junit.jupiter.api.Test;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestAnonymousClass19 extends SmaliTest {
@SuppressWarnings({ "Convert2Lambda", "unused" })
public static class TestCls {
public void test(boolean a, boolean b) {
boolean c = a && b;
use(new Runnable() {
@Override
public void run() {
System.out.println(a + " && " + b + " = " + c);
}
});
}
public void use(Runnable r) {
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("System.out.println(a + \" && \" + b + \" = \" + c);");
}
@Test
public void testSmali() {
assertThat(getClassNodeFromSmaliFiles("ATestCls"))
.code()
.containsOne("System.out.println(a + \" && \" + b + \" = \" + c);");
}
}
@@ -0,0 +1,52 @@
.class public Linner/ATestCls;
.super Ljava/lang/Object;
.method public constructor <init>()V
.registers 1
.prologue
.line 11
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public test(ZZ)V
.registers 5
.param p1, "a" # Z
.param p2, "b" # Z
.prologue
.line 14
if-eqz p1, :cond_e
if-eqz p2, :cond_e
const/4 v0, 0x1
.line 15
.local v0, "c":Z
:goto_5
new-instance v1, Linner/Lambda$TestCls$1;
invoke-direct {v1, p0, p1, p2, v0}, Linner/Lambda$TestCls$1;-><init>(Linner/ATestCls;ZZZ)V
invoke-virtual {p0, v1}, Linner/ATestCls;->use(Ljava/lang/Runnable;)V
.line 21
return-void
.line 14
.end local v0 # "c":Z
:cond_e
const/4 v0, 0x0
goto :goto_5
.end method
.method public use(Ljava/lang/Runnable;)V
.registers 2
return-void
.end method
@@ -0,0 +1,58 @@
.class public final synthetic Linner/Lambda$TestCls$1;
.super Ljava/lang/Object;
.implements Ljava/lang/Runnable;
.field final synthetic this$0:Linner/ATestCls;
.field final synthetic val$a:Z
.field final synthetic val$b:Z
.field final synthetic val$c:Z
.method constructor <init>(Linner/ATestCls;ZZZ)V
.registers 5
.param p1, "this$0"
.annotation system Ldalvik/annotation/Signature;
value = {
"()V"
}
.end annotation
.prologue
.line 15
iput-object p1, p0, Linner/Lambda$TestCls$1;->this$0:Linner/ATestCls;
iput-boolean p2, p0, Linner/Lambda$TestCls$1;->val$a:Z
iput-boolean p3, p0, Linner/Lambda$TestCls$1;->val$b:Z
iput-boolean p4, p0, Linner/Lambda$TestCls$1;->val$c:Z
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public run()V
.registers 4
.prologue
.line 18
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
new-instance v1, Ljava/lang/StringBuilder;
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
iget-boolean v2, p0, Linner/Lambda$TestCls$1;->val$a:Z
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder;
move-result-object v1
const-string v2, " && "
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
iget-boolean v2, p0, Linner/Lambda$TestCls$1;->val$b:Z
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder;
move-result-object v1
const-string v2, " = "
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
iget-boolean v2, p0, Linner/Lambda$TestCls$1;->val$c:Z
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 19
return-void
.end method
@@ -8,6 +8,8 @@ import javax.swing.border.EmptyBorder;
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.TabbedPane;
@@ -23,6 +25,7 @@ import jadx.gui.utils.NLS;
* </ul>
*/
public final class ClassCodeContentPanel extends AbstractCodeContentPanel implements IViewStateSupport {
private static final Logger LOG = LoggerFactory.getLogger(ClassCodeContentPanel.class);
private static final long serialVersionUID = -7229931102504634591L;
private final transient CodePanel javaCodePanel;
@@ -104,7 +107,15 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
boolean isJava = viewState.getSubPath().equals("java");
CodePanel activePanel = isJava ? javaCodePanel : smaliCodePanel;
areaTabbedPane.setSelectedComponent(activePanel);
activePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
activePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
try {
activePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
} catch (Exception e) {
LOG.debug("Failed to restore view position: {}", viewState.getViewPoint(), e);
}
try {
activePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
} catch (Exception e) {
LOG.debug("Failed to restore caret position: {}", viewState.getCaretPos(), e);
}
}
}