diff --git a/archunit/build.gradle b/archunit/build.gradle index 44370c6845..4754ef02b0 100644 --- a/archunit/build.gradle +++ b/archunit/build.gradle @@ -88,7 +88,8 @@ dependencies { runtimeOnly sourceSets.jdk9main.output } -compileJdk9mainJava.with { +compileJdk9mainJava { + dependsOn(compileJava) ext.minimumJavaVersion = JavaVersion.VERSION_1_9 destinationDir = compileJava.destinationDir diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index 3cde461bab..e654e44e4f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -55,7 +55,7 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTypes, HasReturnType, HasTypeParameters, HasThrowsClause { - private final JavaType returnType; + private final ReturnType returnType; private final Parameters parameters; private final String fullName; private final List> typeParameters; @@ -71,7 +71,7 @@ public abstract class JavaCodeUnit JavaCodeUnit(JavaCodeUnitBuilder builder) { super(builder); typeParameters = builder.getTypeParameters(this); - returnType = builder.getReturnType(this); + returnType = new ReturnType(this, builder); parameters = new Parameters(this, builder); fullName = formatMethod(getOwner().getName(), getName(), namesOf(getRawParameterTypes())); referencedClassObjects = ImmutableSet.copyOf(builder.getReferencedClassObjects(this)); @@ -157,13 +157,13 @@ public List getExceptionTypes() { @Override @PublicAPI(usage = ACCESS) public JavaType getReturnType() { - return returnType; + return returnType.get(); } @Override @PublicAPI(usage = ACCESS) public JavaClass getRawReturnType() { - return returnType.toErasure(); + return returnType.getRaw(); } @PublicAPI(usage = ACCESS) @@ -323,6 +323,24 @@ protected List delegate() { } } + private static class ReturnType { + private final JavaClass rawReturnType; + private final JavaType returnType; + + ReturnType(JavaCodeUnit owner, JavaCodeUnitBuilder builder) { + rawReturnType = builder.getRawReturnType(); + returnType = builder.getGenericReturnType(owner); + } + + JavaClass getRaw() { + return rawReturnType; + } + + JavaType get() { + return returnType; + } + } + public static final class Predicates { private Predicates() { } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DependencyResolutionProcess.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DependencyResolutionProcess.java index d032c3c42c..c8adafe049 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DependencyResolutionProcess.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DependencyResolutionProcess.java @@ -124,7 +124,7 @@ void resolve(ImportedClasses classes) { } private void logConfiguration() { - log.info("Automatically resolving transitive class dependencies with the following configuration:{}{}{}{}{}{}", + log.debug("Automatically resolving transitive class dependencies with the following configuration:{}{}{}{}{}{}", formatConfigProperty(MAX_ITERATIONS_FOR_MEMBER_TYPES_PROPERTY_NAME, maxRunsForMemberTypes), formatConfigProperty(MAX_ITERATIONS_FOR_ACCESSES_TO_TYPES_PROPERTY_NAME, maxRunsForAccessesToTypes), formatConfigProperty(MAX_ITERATIONS_FOR_SUPERTYPES_PROPERTY_NAME, maxRunsForSupertypes), diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index fabd004a71..d3bb39e92a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -305,10 +305,14 @@ boolean hasNoParameters() { return rawParameterTypes.isEmpty(); } - public JavaType getReturnType(JavaCodeUnit codeUnit) { + public JavaClass getRawReturnType() { + return get(rawReturnType.getFullyQualifiedClassName()); + } + + public JavaType getGenericReturnType(JavaCodeUnit codeUnit) { return genericReturnType.isPresent() ? genericReturnType.get().finish(codeUnit, allTypeParametersInContextOf(codeUnit), importedClasses) - : get(rawReturnType.getFullyQualifiedClassName()); + : getRawReturnType(); } private Iterable> allTypeParametersInContextOf(JavaCodeUnit codeUnit) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java index c4c3ab7e92..4e1f6bbc6d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java @@ -22,7 +22,6 @@ import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; -import com.tngtech.archunit.core.domain.properties.HasName; import static com.google.common.base.Preconditions.checkNotNull; @@ -139,8 +138,7 @@ public String toString() { public boolean is(JavaCodeUnit method) { return getName().equals(method.getName()) - && getRawParameterTypeNames().equals(HasName.Utils.namesOf(method.getRawParameterTypes())) - && returnType.getFullyQualifiedClassName().equals(method.getRawReturnType().getName()) + && descriptor.equals(method.getDescriptor()) && getDeclaringClassName().equals(method.getOwner().getName()); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java index 25e99d22cb..c90e2f914c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java @@ -101,6 +101,8 @@ import static com.tngtech.archunit.testutil.ReflectionTestUtils.constructor; import static com.tngtech.archunit.testutil.ReflectionTestUtils.field; import static com.tngtech.archunit.testutil.ReflectionTestUtils.method; +import static com.tngtech.archunit.testutil.TestUtils.relativeResourceUri; +import static java.util.Collections.singleton; @RunWith(DataProviderRunner.class) public class ClassFileImporterAccessesTest { @@ -898,6 +900,30 @@ void call(Target target) { .isEqualTo(call.getTarget().getRawParameterTypes()); } + @Test + public void identifies_call_origin_if_signature_and_descriptor_deviate() { + // Kotlin inline functions cause the creation of extra classes where the signature of the respective method shows + // the real types while the descriptor shows the erased types. The erasure of the real types from the signature + // does then not match the descriptor in some cases. + // + // The file `MismatchBetweenDescriptorAndSignature.class` is a byproduct of the following source code: + // -------------------- + // package com.example + // + // object CrashArchUnit { + // fun useInlineFunctionCrashingArchUnit(strings: List) = strings.groupingBy { it.length } + // } + // -------------------- + // With the current Kotlin version this creates a synthetic class file `CrashArchUnit$useInlineFunctionCrashingArchUnit$$inlined$groupingBy$1.class` + // which has been copied and renamed to `MismatchBetweenDescriptorAndSignature.class`. + + JavaClass javaClass = getOnlyElement(new ClassFileImporter().importLocations(singleton(Location.of( + relativeResourceUri(getClass(), "testexamples/MismatchBetweenDescriptorAndSignature.class"))))); + + // this method in the problematic compiled class has a mismatch between return type of descriptor and signature + assertThat(javaClass.getMethod("keyOf", Object.class).getMethodCallsFromSelf()).isNotEmpty(); + } + private Set withoutJavaLangTargets(Set dependencies) { Set result = new HashSet<>(); for (Dependency dependency : dependencies) { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java index ef6502a212..fc141e6859 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java @@ -29,6 +29,7 @@ import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_PACKAGE_INFOS; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_TESTS; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.ONLY_INCLUDE_TESTS; +import static com.tngtech.archunit.testutil.TestUtils.relativeResourceUri; import static com.tngtech.java.junit.dataprovider.DataProviders.$; import static com.tngtech.java.junit.dataprovider.DataProviders.crossProduct; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @@ -168,7 +169,7 @@ public static Object[][] do_not_include_package_info_classes() { @Test @UseDataProvider("do_not_include_package_info_classes") public void detect_package_info_class(ImportOption doNotIncludePackageInfoClasses) throws URISyntaxException { - Location packageInfoLocation = Location.of(getClass().getResource(testExampleResourcePath("package-info.class")).toURI()); + Location packageInfoLocation = Location.of(relativeResourceUri(getClass(), "testexamples/package-info.class")); assertThat(doNotIncludePackageInfoClasses.includes(packageInfoLocation)) .as("doNotIncludePackageInfoClasses includes package-info.class") .isFalse(); @@ -179,10 +180,6 @@ public void detect_package_info_class(ImportOption doNotIncludePackageInfoClasse .isTrue(); } - private String testExampleResourcePath(String resourceName) { - return "/" + getClass().getPackage().getName().replace(".", "/") + "/testexamples/" + resourceName; - } - private static Location locationOf(Class clazz) { return getLast(Locations.ofClass(clazz)); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java index 0636c73474..6aaf7fcd50 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java @@ -84,4 +84,12 @@ public static URL urlOf(Class clazz) { public static URI uriOf(Class clazz) { return toUri(urlOf(clazz)); } + + public static URI relativeResourceUri(Class relativeToClass, String resourceName) { + try { + return relativeToClass.getResource("/" + relativeToClass.getPackage().getName().replace(".", "/") + "/" + resourceName).toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } } diff --git a/archunit/src/test/resources/com/tngtech/archunit/core/importer/testexamples/MismatchBetweenDescriptorAndSignature.class b/archunit/src/test/resources/com/tngtech/archunit/core/importer/testexamples/MismatchBetweenDescriptorAndSignature.class new file mode 100644 index 0000000000..c4012f04c5 Binary files /dev/null and b/archunit/src/test/resources/com/tngtech/archunit/core/importer/testexamples/MismatchBetweenDescriptorAndSignature.class differ