Back to Repositories

Validating Java Signature Parsing Implementation in skylot/jadx

This test suite validates the signature parsing functionality in the JADX decompiler, focusing on Java generic types, wildcards, and method arguments parsing. The tests ensure accurate handling of complex Java type signatures including nested generics and inner classes.

Test Coverage Overview

The test suite provides comprehensive coverage of signature parsing scenarios in Java.

Key areas tested include:
  • Simple Java types and arrays
  • Generic type parameters and bounds
  • Nested and inner generic classes
  • Wildcard types with extends/super bounds
  • Method argument signatures
  • Edge cases and error handling

Implementation Analysis

The testing approach uses JUnit Jupiter framework with systematic verification of parsing results. The implementation follows a methodical pattern of parsing different signature components and validating against expected ArgType objects.

Key patterns include:
  • Dedicated test methods for each signature category
  • Helper methods for repeated verification logic
  • Custom assertions using JadxAssertions
  • Exception testing for invalid signatures

Technical Details

Testing tools and configuration:
  • JUnit Jupiter test framework
  • Custom SignatureParser implementation
  • ArgType class for type representation
  • JadxAssertions for custom verification
  • AssertJ for exception testing

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices.

Notable aspects include:
  • Systematic test organization by signature type
  • Comprehensive edge case coverage
  • Clear test method naming
  • Reusable test utilities
  • Proper exception handling verification
  • Well-documented test cases

skylot/jadx

jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java

            
package jadx.tests.functional;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.exceptions.JadxRuntimeException;

import static jadx.core.dex.instructions.args.ArgType.INT;
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
import static jadx.core.dex.instructions.args.ArgType.array;
import static jadx.core.dex.instructions.args.ArgType.generic;
import static jadx.core.dex.instructions.args.ArgType.genericType;
import static jadx.core.dex.instructions.args.ArgType.object;
import static jadx.core.dex.instructions.args.ArgType.outerGeneric;
import static jadx.core.dex.instructions.args.ArgType.wildcard;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

class SignatureParserTest {

	@Test
	public void testSimpleTypes() {
		checkType("", null);
		checkType("I", INT);
		checkType("[I", array(INT));
		checkType("Ljava/lang/Object;", OBJECT);
		checkType("[Ljava/lang/Object;", array(OBJECT));
		checkType("[[I", array(array(INT)));
	}

	private static void checkType(String str, ArgType type) {
		assertThat(new SignatureParser(str).consumeType()).isEqualTo(type);
	}

	@Test
	public void testGenerics() {
		checkType("TD;", genericType("D"));
		checkType("La<TV;Lb;>;", generic("La;", genericType("V"), object("b")));
		checkType("La<Lb<Lc;>;>;", generic("La;", generic("Lb;", object("Lc;"))));
		checkType("La/b/C<Ld/E<Lf/G;>;>;", generic("La/b/C;", generic("Ld/E;", object("Lf/G;"))));
		checkType("La<TD;>.c;", outerGeneric(generic("La;", genericType("D")), ArgType.object("c")));
		checkType("La<TD;>.c/d;", outerGeneric(generic("La;", genericType("D")), ArgType.object("c.d")));
		checkType("La<Lb;>.c<TV;>;", outerGeneric(generic("La;", object("Lb;")), ArgType.generic("c", genericType("V"))));
	}

	@Test
	public void testInnerGeneric() {
		String signature = "La<TV;>.LinkedHashIterator<Lb$c<Ls;TV;>;>;";
		String objectStr = new SignatureParser(signature).consumeType().getObject();
		assertThat(objectStr).isEqualTo("a$LinkedHashIterator");
	}

	@Test
	public void testNestedInnerGeneric() {
		String signature = "La<TV;>.I.X;";
		ArgType result = new SignatureParser(signature).consumeType();
		assertThat(result.getObject()).isEqualTo("a$I$X");
		// nested 'outerGeneric' objects
		ArgType obj = generic("La;", genericType("V"));
		assertThat(result).isEqualTo(outerGeneric(outerGeneric(obj, object("I")), object("X")));
	}

	@Test
	public void testNestedInnerGeneric2() {
		// full name in inner class
		String signature = "Lsome/long/pkg/ba<Lsome/pkg/s;>.some/long/pkg/bb<Lsome/pkg/p;Lsome/pkg/n;>;";
		ArgType result = new SignatureParser(signature).consumeType();
		System.out.println(result);
		assertThat(result.getObject()).isEqualTo("some.long.pkg.ba$some.long.pkg.bb");
		ArgType baseObj = generic("Lsome/long/pkg/ba;", object("Lsome/pkg/s;"));
		ArgType innerObj = generic("Lsome/long/pkg/bb;", object("Lsome/pkg/p;"), object("Lsome/pkg/n;"));
		ArgType obj = outerGeneric(baseObj, innerObj);
		assertThat(result).isEqualTo(obj);
	}

	@Test
	public void testWildcards() {
		checkWildcards("*", wildcard());
		checkWildcards("+Lb;", wildcard(object("b"), WildcardBound.EXTENDS));
		checkWildcards("-Lb;", wildcard(object("b"), WildcardBound.SUPER));
		checkWildcards("+TV;", wildcard(genericType("V"), WildcardBound.EXTENDS));
		checkWildcards("-TV;", wildcard(genericType("V"), WildcardBound.SUPER));

		checkWildcards("**", wildcard(), wildcard());
		checkWildcards("*Lb;", wildcard(), object("b"));
		checkWildcards("*TV;", wildcard(), genericType("V"));
		checkWildcards("TV;*", genericType("V"), wildcard());
		checkWildcards("Lb;*", object("b"), wildcard());

		checkWildcards("***", wildcard(), wildcard(), wildcard());
		checkWildcards("*Lb;*", wildcard(), object("b"), wildcard());
	}

	private static void checkWildcards(String w, ArgType... types) {
		ArgType parsedType = new SignatureParser("La<" + w + ">;").consumeType();
		ArgType expectedType = generic("La;", types);
		assertThat(parsedType).isEqualTo(expectedType);
	}

	@Test
	public void testGenericMap() {
		checkGenerics("");
		checkGenerics("<T:Ljava/lang/Object;>", "T", emptyList());
		checkGenerics("<K:Ljava/lang/Object;LongType:Ljava/lang/Object;>", "K", emptyList(), "LongType", emptyList());
		checkGenerics("<ResultT:Ljava/lang/Exception;:Ljava/lang/Object;>", "ResultT", singletonList(object("java.lang.Exception")));
	}

	@SuppressWarnings("unchecked")
	private static void checkGenerics(String g, Object... objs) {
		List<ArgType> genericsList = new SignatureParser(g).consumeGenericTypeParameters();
		List<ArgType> expectedList = new ArrayList<>();
		for (int i = 0; i < objs.length; i += 2) {
			String typeVar = (String) objs[i];
			List<ArgType> list = (List<ArgType>) objs[i + 1];
			expectedList.add(ArgType.genericType(typeVar, list));
		}
		assertThat(genericsList).isEqualTo(expectedList);
	}

	@Test
	public void testMethodArgs() {
		List<ArgType> argTypes = new SignatureParser("(Ljava/util/List<*>;)V").consumeMethodArgs(1);

		assertThat(argTypes).hasSize(1);
		assertThat(argTypes.get(0)).isEqualTo(generic("Ljava/util/List;", wildcard()));
	}

	@Test
	public void testMethodArgs2() {
		List<ArgType> argTypes = new SignatureParser("(La/b/C<TT;>.d/E;)V").consumeMethodArgs(1);

		assertThat(argTypes).hasSize(1);
		ArgType argType = argTypes.get(0);
		assertThat(argType.getObject().indexOf('/')).isEqualTo(-1);
		assertThat(argType).isEqualTo(outerGeneric(generic("La/b/C;", genericType("T")), object("d.E")));
	}

	@Test
	public void testBadGenericMap() {
		assertThatExceptionOfType(JadxRuntimeException.class)
				.isThrownBy(() -> new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericTypeParameters());
	}

	@Test
	public void testBadArgs() {
		assertThatExceptionOfType(JadxRuntimeException.class)
				.isThrownBy(() -> new SignatureParser("(TCONTENT)Lpkg/Cls;").consumeMethodArgs(1));
	}
}