TNG / ArchUnit

A Java architecture test library, to specify and assert architecture rules in plain Java
http://archunit.org
Apache License 2.0
3.19k stars 289 forks source link

Class references in lambdas not found #992

Closed wild8oar closed 1 year ago

wild8oar commented 1 year ago

Since version 1.0.0, the method JavaClass#getDirectDependenciesFromSelf no longer finds classes referenced in lambdas.

Unit-Test to reproduce the problem:

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;

import org.junit.jupiter.api.Test;

import com.tngtech.archunit.core.domain.Dependency;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.importer.ClassFileImporter;

class ClassReferencesTest {

    @Test
    void shouldFindAllClassReferences () {
                JavaClass testClass = new ClassFileImporter().importClasses(TestClass.class).get(TestClass.class);

        assertThat(testClass.getDirectDependenciesFromSelf())
            .extracting(Dependency::getTargetClass)
            .extracting(JavaClass::getName)
            .contains(Number.class.getName(), Optional.class.getName(), String.class.getName());
    }

    private static final class TestClass {

        public void doSomething () {
            Class<?> clz = Number.class;  // found
            analyzeClass(Optional.class);  // found
            Runnable runnable = () -> analyzeClass(String.class);  // NOT found
        }

        private void analyzeClass (Class<?> clazz) {
            // nothing
        }
    }
}

While the Number.class and Optional.class are found, the String.class referenced in a lambda is not found:

Expecting ArrayList:
  ["java.lang.Object",
    "java.lang.Object",
    "java.lang.Class",
    "java.lang.Class",
    "java.util.Optional",
    "java.lang.Number"]
to contain:
  ["java.lang.Number", "java.util.Optional", "java.lang.String"]
but could not find the following element(s):
  ["java.lang.String"]

This used to work as expected with version 0.23.1. It could be related to #847, but I cannot see an obvious reason.

Thanks for looking into this!

hankem commented 1 year ago

I can confirm that

class LambdaTest {
    Runnable get() {
        return () -> System.out.println("Hello from `LambdaTest.lambda$get$0()`");
    }
}

assertThat(new ClassFileImporter().importClass(LambdaTest.class).getCodeUnits())
        .extracting(JavaCodeUnit::getFullName)
        .hasSize(3);  // LambdaTest.<init>, LambdaTest.get(), LambdaTest.lambda$get$0()

changed to hasSize(2) (without LambdaTest.lambda$get$0()) with a404fb4b5a948011b7764f5ba0a31426bc98bfe8.

As JavaClass#getCodeUnits() does not include the lambda body anymore, the corresponding dependencies are missing in JavaClass#getDirectDependenciesFromSelf() as well.

codecholeric commented 1 year ago

Thanks for reporting this, this seems to be actually a bug :see_no_evil: AFAIS referenced class objects (and also instanceof checks for that matter) are bypassing the lambda resolution logic. I'll try to fix that ASAP and release a bugfix version!

wild8oar commented 1 year ago

Thanks for your effort, @codecholeric! I'm looking forward to the bugfix release.