From 889a945cf1c8366edf4c856dcaf87bc6eedf8fba Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:26:48 +0100 Subject: [PATCH] fix: additional checks for class signature (#2272) --- .../dex/nodes/parser/SignatureParser.java | 18 +++++ .../core/dex/visitors/SignatureProcessor.java | 72 +++++++++++++------ .../others/TestClassImplementsSignature.java | 32 +++++++++ .../others/TestClassImplementsSignature.raung | 16 +++++ 4 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/others/TestClassImplementsSignature.java create mode 100644 jadx-core/src/test/raung/others/TestClassImplementsSignature.raung diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java index eee3d14a1..718e395b4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java @@ -169,6 +169,24 @@ public class SignatureParser { throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch); } + public List consumeTypeList() { + List 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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java index ccbd6e90e..aac408ee8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java @@ -48,23 +48,49 @@ public class SignatureProcessor extends AbstractVisitor { } try { List generics = sp.consumeGenericTypeParameters(); - ArgType superClass = validateClsType(cls, sp.consumeType(), cls.getSuperClass()); - List 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 interfaces = processInterfaces(cls, sp.consumeTypeList()); + List 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 processInterfaces(ClassNode cls, List parsedTypes) { + List interfaces = cls.getInterfaces(); + if (parsedTypes.isEmpty()) { + return interfaces; + } + int parsedCount = parsedTypes.size(); + int interfacesCount = interfaces.size(); + List 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 */ @@ -106,16 +132,22 @@ public class SignatureProcessor extends AbstractVisitor { return null; } - private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType 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 (!candidateType.isObject()) { - cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls)); - return currentType; + cls.addWarnComment("Incorrect class signature, class is not an object: " + candidateType); + 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; - } - return candidateType; + return true; } private void parseFieldSignature(FieldNode field) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestClassImplementsSignature.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestClassImplementsSignature.java new file mode 100644 index 000000000..dece44c82 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestClassImplementsSignature.java @@ -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 implements Comparable> { + T value; + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("public static abstract class A implements Comparable> {"); + } + + @Test + public void testRaung() { + allowWarnInCode(); + assertThat(getClassNodeFromRaung()) + .code() + .containsOne("public class TestClassImplementsSignature {") + .containsOne("Unexpected interfaces in signature"); + } +} diff --git a/jadx-core/src/test/raung/others/TestClassImplementsSignature.raung b/jadx-core/src/test/raung/others/TestClassImplementsSignature.raung new file mode 100644 index 000000000..2691b0577 --- /dev/null +++ b/jadx-core/src/test/raung/others/TestClassImplementsSignature.raung @@ -0,0 +1,16 @@ +.version 52 # Java 8 +.class public super others/TestClassImplementsSignature +.signature Ljava/lang/Object;Ljava/lang/Comparable;>; + +.field value Ljava/lang/Object; + .signature TT; +.end field + +.method public ()V + .max stack 1 + .max locals 1 + + aload 0 + invokespecial java/lang/Object ()V + return +.end method