Skip to content

Commit

Permalink
Fix JvmClassInfo getReferencedClasses missing types only referenced a…
Browse files Browse the repository at this point in the history
…s method return/arg types
  • Loading branch information
Col-E committed Jun 4, 2024
1 parent 33a72b5 commit 27cce23
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import software.coley.recaf.info.properties.builtin.ReferencedClassesProperty;
import software.coley.recaf.info.properties.builtin.StringDefinitionsProperty;
import software.coley.recaf.util.Types;
import software.coley.recaf.util.visitors.TypeVisitor;

import java.util.*;
import java.util.function.Consumer;
Expand Down Expand Up @@ -66,38 +67,64 @@ default NavigableSet<String> getReferencedClasses() {

Set<String> classNames = new HashSet<>();
ClassReader reader = getClassReader();

// Iterate over pool entries. Supe fast way to discover most of the referenced types.
int itemCount = reader.getItemCount();
char[] buffer = new char[reader.getMaxStringLength()];
for (int i = 1; i < itemCount; i++) {
int offset = reader.getItem(i);
if (offset >= 10) {
int itemTag = reader.readByte(offset - 1);
if (itemTag == ConstantPoolConstants.CLASS) {
String className = reader.readUTF8(offset, buffer);
if (className.isEmpty())
continue;
addName(className, classNames);
} else if (itemTag == ConstantPoolConstants.NAME_TYPE) {
String desc = reader.readUTF8(offset + 2, buffer);
if (desc.isEmpty())
continue;
if (desc.charAt(0) == '(') {
Type methodType = Type.getMethodType(desc);
for (Type argumentType : methodType.getArgumentTypes())
addType(argumentType, classNames);
Type returnType = methodType.getReturnType();
addType(returnType, classNames);
} else {
Type type = Type.getType(desc);
addType(type, classNames);
try {
int itemTag = reader.readByte(offset - 1);
if (itemTag == ConstantPoolConstants.CLASS) {
String className = reader.readUTF8(offset, buffer);
if (className.isEmpty())
continue;
addName(className, classNames);
} else if (itemTag == ConstantPoolConstants.NAME_TYPE) {
String desc = reader.readUTF8(offset + 2, buffer);
if (desc.isEmpty())
continue;
if (desc.charAt(0) == '(') {
addMethodType(Type.getMethodType(desc), classNames);
} else {
Type type = Type.getType(desc);
addType(type, classNames);
}
} else if (itemTag == ConstantPoolConstants.METHOD_TYPE) {
String methodDesc = reader.readUTF8(offset, buffer);
if (methodDesc.isEmpty() || methodDesc.charAt(0) != '(')
continue;
addType(Type.getMethodType(methodDesc), classNames);
}
} catch (Throwable ignored) {
// Exists only to catch situations where obfuscators put unused junk pool entries
// with malformed descriptors, which cause ASM's type parser to crash.
}
}
}

// In some cases like interface classes, there may be UTF8 pool entries outlining method descriptors which
// are not directly linked in NameType or MethodType pool entries. We need to iterate over fields and methods
// to get the descriptors in these cases.
reader.accept(new TypeVisitor(t -> {
if (t.getSort() == Type.METHOD)
addMethodType(t, classNames);
else
addType(t, classNames);
}), ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);

ReferencedClassesProperty.set(this, classNames);
return Objects.requireNonNull(ReferencedClassesProperty.get(this));
}

private static void addMethodType(@Nonnull Type methodType, @Nonnull Set<String> classNames) {
for (Type argumentType : methodType.getArgumentTypes())
addType(argumentType, classNames);
Type returnType = methodType.getReturnType();
addType(returnType, classNames);
}

private static void addType(@Nonnull Type type, @Nonnull Set<String> classNames) {
if (type.getSort() == Type.ARRAY)
type = type.getElementType();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package software.coley.recaf.util.visitors;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.objectweb.asm.*;
import org.slf4j.Logger;
import software.coley.recaf.RecafConstants;
import software.coley.recaf.analytics.logging.Logging;

import java.util.function.Consumer;

/**
* Visitor to accept top-level types of referenced fields, methods, annotations, and nest mates.
*
* @author Matt Coley
*/
public class TypeVisitor extends ClassVisitor {
private static final Logger logger = Logging.get(TypeVisitor.class);
private final Consumer<Type> typeConsumer;

/**
* @param typeConsumer
* Type consumer to accept seen types.
* The same type may be visited multiple times.
* Method types are also passed in.
*/
public TypeVisitor(@Nonnull Consumer<Type> typeConsumer) {
super(RecafConstants.getAsmVersion());
this.typeConsumer = typeConsumer;
}

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if (interfaces != null)
for (String exception : interfaces)
acceptType(exception);
acceptType(superName);
}

@Override
public void visitSource(String source, String debug) {
// no-op
}

@Override
public ModuleVisitor visitModule(String name, int access, String version) {
return null;
}

@Override
public void visitNestHost(String nestHost) {
// no-op
}

@Override
public void visitOuterClass(String owner, String name, String descriptor) {
acceptDescriptor(descriptor);
}

@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
acceptDescriptor(descriptor);
return null;
}

@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
acceptDescriptor(descriptor);
return null;
}

@Override
public void visitAttribute(Attribute attribute) {
// no-op
}

@Override
public void visitNestMember(String nestMember) {
acceptType(nestMember);
}

@Override
public void visitPermittedSubclass(String permittedSubclass) {
acceptType(permittedSubclass);
}

@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// no-op
}

@Override
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
acceptDescriptor(descriptor);
return null;
}

@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
acceptDescriptor(descriptor);
return null;
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
acceptDescriptor(descriptor);
if (exceptions != null)
for (String exception : exceptions)
acceptType(exception);
return null;
}

private void acceptType(@Nullable String internalName) {
if (internalName == null || internalName.isEmpty()) return;

try {
Type methodType = Type.getObjectType(internalName);
typeConsumer.accept(methodType);
} catch (Throwable t) {
logger.trace("Ignored invalid internal name: {}", internalName, t);
}
}

private void acceptDescriptor(@Nullable String descriptor) {
if (descriptor == null || descriptor.isEmpty()) return;

try {
if (descriptor.charAt(0) == '(') {
Type methodType = Type.getMethodType(descriptor);
typeConsumer.accept(methodType);
} else {
Type type = Type.getType(descriptor);
typeConsumer.accept(type);
}
} catch (Throwable t) {
logger.trace("Ignored invalid type: {}", descriptor, t);
}
}
}

0 comments on commit 27cce23

Please sign in to comment.