Back to Repositories

Validating Type Comparison System Implementation in skylot/jadx

This test suite validates type comparison functionality in the JADX decompiler, focusing on Java type inference and generic type handling. It ensures accurate type comparison across different scenarios including primitive types, arrays, generics, and wildcards.

Test Coverage Overview

The test suite provides comprehensive coverage of type comparison scenarios in JADX.

Key areas tested include:
  • Primitive type comparisons
  • Array type handling
  • Generic type inference
  • Wildcard type comparisons
  • Outer generic class handling
Edge cases covered include nested generics, bounded wildcards, and type variable inheritance.

Implementation Analysis

The testing approach utilizes JUnit Jupiter framework with systematic test organization. Each test method focuses on specific type comparison scenarios, using a consistent pattern of type definition and comparison verification.

Technical implementation features:
  • BeforeEach setup for TypeCompare instance
  • Custom assertion utilities
  • Extensive use of static imports for ArgType definitions
  • Logging integration for debugging

Technical Details

Testing infrastructure includes:
  • JUnit Jupiter test framework
  • SLF4J logging framework
  • Custom NotYetImplementedExtension
  • JadxArgs and RootNode initialization
  • Custom assertion utilities via JadxAssertions

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through methodical organization and thorough validation.

Notable practices include:
  • Systematic test method naming
  • Comprehensive documentation
  • Modular test cases
  • Proper test initialization
  • Consistent assertion patterns
  • Detailed logging for debugging

skylot/jadx

jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java

            
package jadx.core.dex.visitors.typeinference;

import java.util.Collections;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.NotYetImplementedExtension;
import jadx.api.JadxArgs;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
import jadx.core.dex.nodes.RootNode;

import static jadx.core.dex.instructions.args.ArgType.BYTE;
import static jadx.core.dex.instructions.args.ArgType.CHAR;
import static jadx.core.dex.instructions.args.ArgType.CLASS;
import static jadx.core.dex.instructions.args.ArgType.EXCEPTION;
import static jadx.core.dex.instructions.args.ArgType.INT;
import static jadx.core.dex.instructions.args.ArgType.NARROW;
import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL;
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
import static jadx.core.dex.instructions.args.ArgType.STRING;
import static jadx.core.dex.instructions.args.ArgType.THROWABLE;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_ARRAY;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_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.wildcard;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;

@ExtendWith(NotYetImplementedExtension.class)
public class TypeCompareTest {
	private static final Logger LOG = LoggerFactory.getLogger(TypeCompareTest.class);

	private TypeCompare compare;

	@BeforeEach
	public void init() {
		JadxArgs args = new JadxArgs();
		RootNode root = new RootNode(args);
		root.loadClasses(Collections.emptyList());
		root.initClassPath();
		compare = new TypeCompare(root);
	}

	@Test
	public void compareTypes() {
		check(INT, UNKNOWN_OBJECT, TypeCompareEnum.CONFLICT);
		check(INT, OBJECT, TypeCompareEnum.CONFLICT);

		firstIsNarrow(INT, UNKNOWN);
		firstIsNarrow(CHAR, NARROW_INTEGRAL);

		firstIsNarrow(array(UNKNOWN), UNKNOWN);
		firstIsNarrow(array(UNKNOWN), NARROW);
		firstIsNarrow(array(CHAR), UNKNOWN_OBJECT);
	}

	@Test
	public void compareArrays() {
		firstIsNarrow(array(CHAR), OBJECT);
		firstIsNarrow(array(CHAR), array(UNKNOWN));

		firstIsNarrow(array(OBJECT), OBJECT);
		firstIsNarrow(array(OBJECT), array(UNKNOWN_OBJECT));
		firstIsNarrow(array(STRING), array(UNKNOWN_OBJECT));
		firstIsNarrow(array(STRING), array(OBJECT));

		firstIsNarrow(UNKNOWN_ARRAY, OBJECT);

		firstIsNarrow(array(BYTE), OBJECT);
		firstIsNarrow(array(array(BYTE)), array(OBJECT));

		check(array(OBJECT), array(INT), TypeCompareEnum.CONFLICT);

		ArgType integerType = object("java.lang.Integer");
		check(array(OBJECT), array(integerType), TypeCompareEnum.WIDER);
		check(array(INT), array(integerType), TypeCompareEnum.CONFLICT);
		check(array(INT), array(INT), TypeCompareEnum.EQUAL);

		ArgType wildClass = generic(CLASS, wildcard());
		check(array(wildClass), array(CLASS), TypeCompareEnum.NARROW_BY_GENERIC);
		check(array(CLASS), array(wildClass), TypeCompareEnum.WIDER_BY_GENERIC);
	}

	@Test
	public void compareGenerics() {
		ArgType mapCls = object("java.util.Map");
		ArgType setCls = object("java.util.Set");

		ArgType keyType = genericType("K");
		ArgType valueType = genericType("V");
		ArgType mapGeneric = ArgType.generic(mapCls.getObject(), keyType, valueType);

		check(mapCls, mapGeneric, TypeCompareEnum.WIDER_BY_GENERIC);
		check(mapCls, setCls, TypeCompareEnum.CONFLICT);

		ArgType setGeneric = ArgType.generic(setCls.getObject(), valueType);
		ArgType setWildcard = ArgType.generic(setCls.getObject(), ArgType.wildcard());

		check(setWildcard, setGeneric, TypeCompareEnum.CONFLICT);
		check(setWildcard, setCls, TypeCompareEnum.NARROW_BY_GENERIC);
		// TODO implement compare for wildcard with bounds
	}

	@Test
	public void compareWildCards() {
		ArgType clsWildcard = generic(CLASS.getObject(), wildcard());
		check(clsWildcard, CLASS, TypeCompareEnum.NARROW_BY_GENERIC);

		ArgType clsExtendedWildcard = generic(CLASS.getObject(), wildcard(STRING, WildcardBound.EXTENDS));
		check(clsWildcard, clsExtendedWildcard, TypeCompareEnum.WIDER);

		ArgType listWildcard = generic(CLASS.getObject(), wildcard(object("java.util.List"), WildcardBound.EXTENDS));
		ArgType collWildcard = generic(CLASS.getObject(), wildcard(object("java.util.Collection"), WildcardBound.EXTENDS));
		check(listWildcard, collWildcard, TypeCompareEnum.NARROW);

		ArgType collSuperWildcard = generic(CLASS.getObject(), wildcard(object("java.util.Collection"), WildcardBound.SUPER));
		check(collSuperWildcard, listWildcard, TypeCompareEnum.CONFLICT);
	}

	@Test
	public void compareGenericWildCards() {
		// 'java.util.List<T>' and 'java.util.List<? extends T>'
		ArgType listCls = object("java.util.List");
		ArgType genericType = genericType("T");
		ArgType genericList = generic(listCls, genericType);
		ArgType genericExtendedList = generic(listCls, wildcard(genericType, WildcardBound.EXTENDS));
		check(genericList, genericExtendedList, TypeCompareEnum.CONFLICT_BY_GENERIC);
	}

	@Test
	public void compareGenericTypes() {
		ArgType vType = genericType("V");
		check(vType, OBJECT, TypeCompareEnum.NARROW);
		check(vType, STRING, TypeCompareEnum.CONFLICT);

		ArgType rType = genericType("R");
		check(vType, rType, TypeCompareEnum.CONFLICT);
		check(vType, vType, TypeCompareEnum.EQUAL);

		ArgType tType = genericType("T");
		ArgType tStringType = genericType("T", STRING);

		check(tStringType, STRING, TypeCompareEnum.NARROW);
		check(tStringType, OBJECT, TypeCompareEnum.NARROW);
		check(tStringType, tType, TypeCompareEnum.NARROW);

		ArgType tObjType = genericType("T", OBJECT);

		check(tObjType, OBJECT, TypeCompareEnum.NARROW);
		check(tObjType, tType, TypeCompareEnum.EQUAL);

		check(tStringType, tObjType, TypeCompareEnum.NARROW);
	}

	@Test
	public void compareGenericTypes2() {
		ArgType npeType = object("java.lang.NullPointerException");

		// check clsp graph
		check(npeType, THROWABLE, TypeCompareEnum.NARROW);
		check(npeType, EXCEPTION, TypeCompareEnum.NARROW);
		check(EXCEPTION, THROWABLE, TypeCompareEnum.NARROW);

		ArgType typeVar = genericType("T", EXCEPTION); // T extends Exception

		// target checks
		check(THROWABLE, typeVar, TypeCompareEnum.WIDER);
		check(EXCEPTION, typeVar, TypeCompareEnum.WIDER);
		check(npeType, typeVar, TypeCompareEnum.NARROW);
	}

	@Test
	public void compareOuterGenerics() {
		ArgType hashMapType = object("java.util.HashMap");
		ArgType innerEntrySetType = object("EntrySet");
		ArgType firstInstance = ArgType.outerGeneric(generic(hashMapType, STRING, STRING), innerEntrySetType);
		ArgType secondInstance = ArgType.outerGeneric(generic(hashMapType, OBJECT, OBJECT), innerEntrySetType);

		check(firstInstance, secondInstance, TypeCompareEnum.NARROW);
	}

	private void firstIsNarrow(ArgType first, ArgType second) {
		check(first, second, TypeCompareEnum.NARROW);
	}

	private void check(ArgType first, ArgType second, TypeCompareEnum expectedResult) {
		LOG.debug("Compare: '{}' and '{}', expect: '{}'", first, second, expectedResult);

		assertThat(compare.compareTypes(first, second))
				.as("Compare '%s' and '%s'", first, second)
				.isEqualTo(expectedResult);

		assertThat(compare.compareTypes(second, first))
				.as("Compare '%s' and '%s'", second, first)
				.isEqualTo(expectedResult.invert());
	}
}