Skip to content

Commit

Permalink
Fix class signature handling
Browse files Browse the repository at this point in the history
  • Loading branch information
bengt-GS authored and daphnis.chevreton committed Jul 5, 2024
1 parent d068760 commit 54f85da
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ public class ClassReferenceInitializer

private final KotlinReferenceInitializer kotlinReferenceInitializer;

private final InvalidSignatureCleaner invalidSignatureCleaner = new InvalidSignatureCleaner();

/**
* Creates a new ClassReferenceInitializer that initializes the references of all visited class
* files.
Expand Down Expand Up @@ -254,6 +256,10 @@ public void visitProgramClass(ProgramClass programClass) {
programClass.fieldsAccept(this);
programClass.methodsAccept(this);

// Clean up invalid signatures. We cannot do this while initializing the other attributes, since
// that will result in possibly modifying the attributes while we are iterating over them.
programClass.attributesAccept(invalidSignatureCleaner);

// Initialize the attributes.
programClass.attributesAccept(this);

Expand All @@ -278,6 +284,10 @@ public void visitProgramField(ProgramClass programClass, ProgramField programFie
programField.referencedClass =
findReferencedClass(programClass, programField.getDescriptor(programClass));

// Clean up invalid signatures. We cannot do this while initializing the other attributes, since
// that will result in possibly modifying the attributes while we are iterating over them.
programField.attributesAccept(programClass, invalidSignatureCleaner);

// Initialize the attributes.
programField.attributesAccept(programClass, this);
}
Expand All @@ -287,6 +297,10 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM
programMethod.referencedClasses =
findReferencedClasses(programClass, programMethod.getDescriptor(programClass));

// Clean up invalid signatures. We cannot do this while initializing the other attributes, since
// that will result in possibly modifying the attributes while we are iterating over them.
programMethod.attributesAccept(programClass, invalidSignatureCleaner);

// Initialize the attributes.
programMethod.attributesAccept(programClass, this);
}
Expand Down Expand Up @@ -519,37 +533,21 @@ public void visitLocalVariableTypeTableAttribute(
@Override
public void visitSignatureAttribute(
Clazz clazz, Member member, SignatureAttribute signatureAttribute) {
try {
signatureAttribute.referencedClasses =
findReferencedClasses(clazz, signatureAttribute.getSignature(clazz));
} catch (Exception corruptSignature) {
// #2468: delete corrupt signature attributes, since they
// cannot be otherwise worked around.
member.accept(clazz, new NamedAttributeDeleter(Attribute.SIGNATURE));
}
signatureAttribute.referencedClasses =
findReferencedClasses(clazz, signatureAttribute.getSignature(clazz));
}

@Override
public void visitSignatureAttribute(
Clazz clazz, RecordComponentInfo recordComponentInfo, SignatureAttribute signatureAttribute) {
try {
signatureAttribute.referencedClasses =
findReferencedClasses(clazz, signatureAttribute.getSignature(clazz));
} catch (Exception corruptSignature) {
// #2468: delete corrupt signature attributes, since they
// cannot be otherwise worked around.
recordComponentInfo.attributesAccept(clazz, new NamedAttributeDeleter(Attribute.SIGNATURE));
}
signatureAttribute.referencedClasses =
findReferencedClasses(clazz, signatureAttribute.getSignature(clazz));
}

@Override
public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) {
if (isValidClassSignature(clazz, signatureAttribute.getSignature(clazz))) {
signatureAttribute.referencedClasses =
findReferencedClasses(clazz, signatureAttribute.getSignature(clazz));
} else {
clazz.accept(new NamedAttributeDeleter(Attribute.SIGNATURE));
}
signatureAttribute.referencedClasses =
findReferencedClasses(clazz, signatureAttribute.getSignature(clazz));
}

@Override
Expand Down Expand Up @@ -581,6 +579,10 @@ public void visitRecordComponentInfo(Clazz clazz, RecordComponentInfo recordComp

recordComponentInfo.referencedField = memberFinder.findField(clazz, name, descriptor);

// Clean up invalid signatures. We cannot do this while initializing the other attributes, since
// that will result in possibly modifying the attributes while we are iterating over them.
recordComponentInfo.attributesAccept(clazz, invalidSignatureCleaner);

// Initialize the attributes.
recordComponentInfo.attributesAccept(clazz, this);
}
Expand Down Expand Up @@ -1273,6 +1275,84 @@ private Clazz findClass(Clazz referencingClass, String name, boolean report) {
return clazz;
}

/** A small utility class used to clean up invalid signatures. */
private class InvalidSignatureCleaner implements AttributeVisitor {
/**
* Perform some sanity checks on the Signature and whether it follows the JVM specification.
* TODO: T4517
*/
private boolean isValidClassSignature(Clazz clazz, String signature) {
try {
// Loop through the signature to if it can be parsed.
new DescriptorClassEnumeration(signature).classCount();

// Then check whether the listed types are as expected.
InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(signature);

if (!internalTypeEnumeration.hasMoreTypes()) {
return false;
}

String superName = clazz.getSuperName();
String signSuperName =
ClassUtil.internalClassNameFromClassSignature(internalTypeEnumeration.nextType());
if (superName != null && !signSuperName.startsWith(superName)) {
return false;
}

// We are assuming interfaces in the descriptor occur in the same order as they are defined
// on the class file level. While this is very likely in the vast majority of cases, it
// doesn't seem like the JVM actually checks for this. TODO: see T4517.
for (int i = 0; i < clazz.getInterfaceCount(); i++) {
if (!internalTypeEnumeration.hasMoreTypes()) {
return false;
}

String intfName = clazz.getInterfaceName(i);
String signIntfName =
ClassUtil.internalClassNameFromClassSignature(internalTypeEnumeration.nextType());
if (!signIntfName.startsWith(intfName)) {
return false;
}
}

return !internalTypeEnumeration.hasMoreTypes();
} catch (Exception corruptedSignature) {
return false;
}
}

// Implementations for AttributeVisitor.

@Override
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}

@Override
public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) {
if (!isValidClassSignature(clazz, signatureAttribute.getSignature(clazz))) {
clazz.accept(new NamedAttributeDeleter(Attribute.SIGNATURE));
}
}

@Override
public void visitSignatureAttribute(
Clazz clazz,
RecordComponentInfo recordComponentInfo,
SignatureAttribute signatureAttribute) {
if (!isValidClassSignature(clazz, signatureAttribute.getSignature(clazz))) {
recordComponentInfo.attributesAccept(clazz, new NamedAttributeDeleter(Attribute.SIGNATURE));
}
}

@Override
public void visitSignatureAttribute(
Clazz clazz, Member member, SignatureAttribute signatureAttribute) {
if (!isValidClassSignature(clazz, signatureAttribute.getSignature(clazz))) {
member.accept(clazz, new NamedAttributeDeleter(Attribute.SIGNATURE));
}
}
}

// Helper classes for KotlinReferenceInitializer.

public static class KotlinTypeAliasReferenceInitializer
Expand Down Expand Up @@ -1632,45 +1712,6 @@ private void initProperty(Clazz thisClazz, Clazz parentClazz) {
}
}

/** Perform some sanity checks on the Signature and whether it follows the JVM specification. */
private boolean isValidClassSignature(Clazz clazz, String signature) {
try {
// Loop through the signature to if it can be parsed.
new DescriptorClassEnumeration(signature).classCount();

// Then check whether the listed types are as expected.
InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(signature);

if (!internalTypeEnumeration.hasMoreTypes()) {
return false;
}

String superName = clazz.getSuperName();
String signSuperName =
ClassUtil.internalClassNameFromClassType(internalTypeEnumeration.nextType());
if (superName != null && !signSuperName.startsWith(superName)) {
return false;
}

for (int i = 0; i < clazz.getInterfaceCount(); i++) {
if (!internalTypeEnumeration.hasMoreTypes()) {
return false;
}

String intfName = clazz.getInterfaceName(i);
String signIntfName =
ClassUtil.internalClassNameFromClassType(internalTypeEnumeration.nextType());
if (!signIntfName.startsWith(intfName)) {
return false;
}
}

return !internalTypeEnumeration.hasMoreTypes();
} catch (Exception corruptedSignature) {
return false;
}
}

/**
* This {@link InvalidReferenceVisitor} will print out missing references to the supplied warning
* printers.
Expand Down
45 changes: 44 additions & 1 deletion base/src/main/java/proguard/classfile/util/ClassUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,9 @@ public static String internalTypeFromClassType(String internalType) {

/**
* Returns the internal class name of a given internal class type (including an array type). Types
* involving primitive types are returned unchanged.
* involving primitive types are returned unchanged. </br> Note: If you are parsing a Signature
* attribute string, use {@link ClassUtil#internalClassNameFromClassSignature(String)} instead, as
* this method will not handle generic type parameters and the '.' as inner class separator.
*
* @param internalClassType the internal class type, e.g. "<code>[Ljava/lang/Object;</code>", "
* <code>Ljava/lang/Object;</code>", or "<code>java/lang/Object</code>".
Expand All @@ -602,6 +604,47 @@ public static String internalClassNameFromClassType(String internalClassType) {
: internalClassType;
}

/**
* Returns the internal class name for a given Class Signature.
*
* @param classSignature the class signature, e.g. "<code>Lsome/package/Klass< T;>.Inner</code>"
* @return the internal class name, e.g. "<code>some/package/Klass$Inner</code>".
*/
public static String internalClassNameFromClassSignature(String classSignature) {
// Remove generic type information.
String internalClassName = removeGenericTypes(classSignature);

// Remove the 'L' and ';' bits.
internalClassName = internalClassNameFromClassType(internalClassName);

// Convert each '.' to a '$', as dictated by the JVM spec.
// See: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4
return internalClassName.replace('.', '$');
}

/** Remove any generic type parameters from the given descriptor. */
public static String removeGenericTypes(String descriptor) {
int start = descriptor.indexOf('<');
int end = descriptor.lastIndexOf('>');

if (start < end && start >= 0) {
int index = start;

while (index < end) {
index++;
if (descriptor.charAt(index) == '<') {
start = index;
} else if (descriptor.charAt(index) == '>') {
// Found a closing tag, by this point it has to be the most inner one.
return removeGenericTypes(
descriptor.substring(0, start) + descriptor.substring(index + 1));
}
}
}

return descriptor;
}

/**
* Returns the internal class name of any given internal descriptor type, disregarding array
* prefixes.
Expand Down
Loading

0 comments on commit 54f85da

Please sign in to comment.