Created
July 23, 2021 13:33
-
-
Save Geolykt/9bbbb6d2a591d3657269ad6d4c5c17c8 to your computer and use it in GitHub Desktop.
A failed attempt at solving some <undefinedtype> mentions. Perhaps one day someone will get this to work, which is why I pushed it here. However it is more likely that it just does not work and the idea was super dumb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Guesses anonymous inner classes by checking whether they have a synthetic field and if they | |
* do whether they are referenced only by a single "parent" class. | |
* Note: this method is VERY aggressive when it comes to adding inner classes, sometimes it adds | |
* inner classes on stuff where it wouldn't belong. This means that useage of this method should | |
* be done wiseley. This method will do some damage even if it does no good. | |
* | |
* @param doLog whether to perfom any logging via System.out | |
*/ | |
public void guessAnonymousInnerClasses(boolean doLog) { | |
long start = System.currentTimeMillis(); | |
// Class name -> referenced class | |
HashMap<String, String> candidates = new HashMap<>(); | |
for (ClassNode node : nodes) { | |
if ((node.access & ACCESS_FLAGS) != 0) { | |
continue; // Anonymous inner classes are always package-private | |
} | |
boolean skipClass = false; | |
FieldNode outerClassReference = null; | |
for (FieldNode field : node.fields) { | |
final int requiredFlags = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL; | |
if ((field.access & requiredFlags) == requiredFlags | |
&& (field.access & ACCESS_FLAGS) == 0) { | |
if (outerClassReference != null) { | |
skipClass = true; | |
break; // short-circuit | |
} | |
outerClassReference = field; | |
} | |
} | |
if (skipClass || outerClassReference == null) { | |
continue; | |
} | |
// anonymous classes can only have a single constructor since they are only created at a single spot | |
// However they also have to have a constructor so they can pass the outer class reference | |
MethodNode constructor = null; | |
for (MethodNode method : node.methods) { | |
if (method.name.equals("<init>")) { | |
if (constructor != null) { | |
// cant have multiple constructors | |
skipClass = true; | |
break; // short-circuit | |
} | |
if ((method.access & ACCESS_FLAGS) != 0) { | |
// The constructor should be package - protected | |
skipClass = true; | |
break; | |
} | |
constructor = method; | |
} | |
} | |
if (skipClass || constructor == null) { // require a single constructor, not more, not less | |
continue; | |
} | |
// since we have the potential reference to the outer class and we know that it has to be set | |
// via the constructor's parameter, we can check whether this is the case here | |
DescString desc = new DescString(constructor.desc); | |
skipClass = true; | |
while (desc.hasNext()) { | |
String type = desc.nextType(); | |
if (type.equals(outerClassReference.desc)) { | |
skipClass = false; | |
break; | |
} | |
} | |
if (skipClass) { | |
continue; | |
} | |
if (node.name.indexOf('$') != -1 && !Character.isDigit(node.name.charAt(node.name.length() - 1))) { | |
// Unobfuscated class that is 100% not anonymous | |
continue; | |
} | |
candidates.put(node.name, null); | |
} | |
// Make sure that the constructor is only invoked in a single class, which should be the outer class | |
for (ClassNode node : nodes) { | |
for (MethodNode method : node.methods) { | |
AbstractInsnNode instruction = method.instructions.getFirst(); | |
while (instruction != null) { | |
if (instruction instanceof MethodInsnNode && ((MethodInsnNode)instruction).name.equals("<init>")) { | |
MethodInsnNode methodInvocation = (MethodInsnNode) instruction; | |
String owner = methodInvocation.owner; | |
if (candidates.containsKey(owner)) { | |
if (owner.equals(node.name)) { | |
// this is no really valid anonymous class | |
candidates.remove(owner); | |
} else { | |
String invoker = candidates.get(owner); | |
if (invoker == null) { | |
candidates.put(owner, node.name); | |
} else if (!invoker.equals(node.name)) { | |
// constructor referenced by multiple classes, cannot be valid | |
// However apparently these classes could be extended? I am not entirely sure how that is possible, but it is. | |
// That being said, we are going to ignore that this is possible and just consider them invalid | |
// as everytime this happens the decompiler is able to decompile the class without any issues. | |
candidates.remove(owner); | |
} | |
} | |
} | |
} | |
instruction = instruction.getNext(); | |
} | |
} | |
} | |
int addedInners = 0; | |
for (Map.Entry<String, String> candidate : candidates.entrySet()) { | |
String inner = candidate.getKey(); | |
String outer = candidate.getValue(); | |
if (outer == null) { | |
continue; | |
} | |
ClassNode innerNode = nameToNode.get(inner); | |
ClassNode outernode = nameToNode.get(outer); | |
if (outernode == null) { | |
continue; | |
} | |
boolean hasInnerClassInfoInner = false; | |
for (InnerClassNode icn : innerNode.innerClasses) { | |
if (icn.name.equals(inner)) { | |
hasInnerClassInfoInner = true; | |
break; | |
} | |
} | |
boolean hasInnerClassInfoOuter = false; | |
for (InnerClassNode icn : outernode.innerClasses) { | |
if (icn.name.equals(inner)) { | |
hasInnerClassInfoOuter = true; | |
break; | |
} | |
} | |
if (hasInnerClassInfoInner && hasInnerClassInfoOuter) { | |
continue; | |
} | |
InnerClassNode newInnerClassNode = new InnerClassNode(inner, outer, null, 16400); | |
if (!hasInnerClassInfoInner) { | |
innerNode.innerClasses.add(newInnerClassNode); | |
} | |
if (!hasInnerClassInfoOuter) { | |
innerNode.innerClasses.add(newInnerClassNode); | |
} | |
addedInners++; | |
} | |
if (doLog) { | |
System.out.printf(Locale.ROOT, "Added %d inner class nodes for anonymous classes. (%d ms)\n", addedInners, System.currentTimeMillis() - start); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I feel so dumb now. Anyways, this code was reintegrated into standard Oaktree