Skip to content

Instantly share code, notes, and snippets.

@Geolykt
Created July 23, 2021 13:33
Show Gist options
  • Save Geolykt/9bbbb6d2a591d3657269ad6d4c5c17c8 to your computer and use it in GitHub Desktop.
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
/**
* 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);
}
}
@Geolykt
Copy link
Author

Geolykt commented Jul 24, 2021

I feel so dumb now. Anyways, this code was reintegrated into standard Oaktree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment