Skip to content

Commit

Permalink
fix: additional checks for class signature (#2272)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Sep 12, 2024
1 parent fd80e03 commit 889a945
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,24 @@ public ArgType consumeType() {
throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch);
}

public List<ArgType> consumeTypeList() {
List<ArgType> list = null;
while (true) {
ArgType type = consumeType();
if (type == null) {
break;
}
if (list == null) {
list = new ArrayList<>();
}
list.add(type);
}
if (list == null) {
return Collections.emptyList();
}
return list;
}

private ArgType consumeObjectType(boolean innerType) {
mark();
int ch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,49 @@ private void parseClassSignature(ClassNode cls) {
}
try {
List<ArgType> generics = sp.consumeGenericTypeParameters();
ArgType superClass = validateClsType(cls, sp.consumeType(), cls.getSuperClass());
List<ArgType> interfaces = cls.getInterfaces();
for (int i = 0; i < interfaces.size(); i++) {
ArgType type = sp.consumeType();
if (type != null) {
interfaces.set(i, validateClsType(cls, type, interfaces.get(i)));
} else {
break;
}
}
generics = fixTypeParamDeclarations(cls, generics, superClass, interfaces);
cls.updateGenericClsData(generics, superClass, interfaces);
ArgType superClass = processSuperType(cls, sp.consumeType());
List<ArgType> interfaces = processInterfaces(cls, sp.consumeTypeList());
List<ArgType> resultGenerics = fixTypeParamDeclarations(cls, generics, superClass, interfaces);
cls.updateGenericClsData(resultGenerics, superClass, interfaces);
} catch (Exception e) {
cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e);
}
}

private ArgType processSuperType(ClassNode cls, ArgType parsedType) {
ArgType superType = cls.getSuperClass();
if (Objects.equals(parsedType.getObject(), cls.getClassInfo().getType().getObject())) {
cls.addWarnComment("Incorrect class signature: super class is equals to this class");
return superType;
}
return bestClsType(cls, parsedType, superType);
}

/**
* Parse, validate and update class interfaces types.
*/
private List<ArgType> processInterfaces(ClassNode cls, List<ArgType> parsedTypes) {
List<ArgType> interfaces = cls.getInterfaces();
if (parsedTypes.isEmpty()) {
return interfaces;
}
int parsedCount = parsedTypes.size();
int interfacesCount = interfaces.size();
List<ArgType> result = new ArrayList<>(interfacesCount);
int count = Math.min(interfacesCount, parsedCount);
for (int i = 0; i < interfacesCount; i++) {
if (i < count) {
result.add(bestClsType(cls, parsedTypes.get(i), interfaces.get(i)));
} else {
result.add(interfaces.get(i));
}
}
if (interfacesCount < parsedCount) {
cls.addWarnComment("Unexpected interfaces in signature: " + parsedTypes.subList(interfacesCount, parsedCount));
}
return result;
}

/**
* Add missing type parameters from super type and interfaces to make code compilable
*/
Expand Down Expand Up @@ -106,16 +132,22 @@ private static List<ArgType> fixTypeParamDeclarations(ClassNode cls,
return null;
}

private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
if (!candidateType.isObject()) {
cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls));
return currentType;
private ArgType bestClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
if (validateClsType(cls, candidateType)) {
return candidateType;
}
return currentType;
}

private boolean validateClsType(ClassNode cls, ArgType candidateType) {
if (candidateType == null) {
return false;
}
if (Objects.equals(candidateType.getObject(), cls.getClassInfo().getType().getObject())) {
cls.addWarnComment("Incorrect class signature, class is equals to this class: " + SignatureParser.getSignature(cls));
return currentType;
if (!candidateType.isObject()) {
cls.addWarnComment("Incorrect class signature, class is not an object: " + candidateType);
return false;
}
return candidateType;
return true;
}

private void parseFieldSignature(FieldNode field) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package jadx.tests.integration.others;

import org.junit.jupiter.api.Test;

import jadx.tests.api.RaungTest;

import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;

public class TestClassImplementsSignature extends RaungTest {

public static class TestCls {
public abstract static class A<T> implements Comparable<A<T>> {
T value;
}
}

@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("public static abstract class A<T> implements Comparable<A<T>> {");
}

@Test
public void testRaung() {
allowWarnInCode();
assertThat(getClassNodeFromRaung())
.code()
.containsOne("public class TestClassImplementsSignature<T> {")
.containsOne("Unexpected interfaces in signature");
}
}
16 changes: 16 additions & 0 deletions jadx-core/src/test/raung/others/TestClassImplementsSignature.raung
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.version 52 # Java 8
.class public super others/TestClassImplementsSignature
.signature <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/Comparable<Lothers/TestClassImplementsSignature<LT;>;>;

.field value Ljava/lang/Object;
.signature TT;
.end field

.method public <init>()V
.max stack 1
.max locals 1

aload 0
invokespecial java/lang/Object <init> ()V
return
.end method

0 comments on commit 889a945

Please sign in to comment.