diff --git a/src/it/kotlinInnerClassReference/consumer/pom.xml b/src/it/kotlinInnerClassReference/consumer/pom.xml new file mode 100644 index 0000000..7786632 --- /dev/null +++ b/src/it/kotlinInnerClassReference/consumer/pom.xml @@ -0,0 +1,100 @@ + + + + + + 4.0.0 + + + org.apache.maven.shared.dependency-analyzer.tests + kotlin-inner-class-reference + 1.0 + + + kotlin-consumer + + + 2.4.10 + + + + + org.apache.maven.shared.dependency-analyzer.tests + nested-class-library + ${project.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + kotlin-sources + initialize + + add-source + + + + src/main/kotlin + + + + + + + org.apache.maven.shared.dependency-analyzer.tests + maven-mock-plugin + 1.0 + + + + mock-analyze + + + + + + + diff --git a/src/it/kotlinInnerClassReference/consumer/src/main/kotlin/consumer/Example.kt b/src/it/kotlinInnerClassReference/consumer/src/main/kotlin/consumer/Example.kt new file mode 100644 index 0000000..62f35b3 --- /dev/null +++ b/src/it/kotlinInnerClassReference/consumer/src/main/kotlin/consumer/Example.kt @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package consumer + +import library.Outer + +class Example { + fun createInner() { + Outer.Inner() + } +} diff --git a/src/it/kotlinInnerClassReference/library/pom.xml b/src/it/kotlinInnerClassReference/library/pom.xml new file mode 100644 index 0000000..0f36940 --- /dev/null +++ b/src/it/kotlinInnerClassReference/library/pom.xml @@ -0,0 +1,34 @@ + + + + + + 4.0.0 + + + org.apache.maven.shared.dependency-analyzer.tests + kotlin-inner-class-reference + 1.0 + + + nested-class-library + diff --git a/src/it/kotlinInnerClassReference/library/src/main/java/library/Outer.java b/src/it/kotlinInnerClassReference/library/src/main/java/library/Outer.java new file mode 100644 index 0000000..2e0aca1 --- /dev/null +++ b/src/it/kotlinInnerClassReference/library/src/main/java/library/Outer.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package library; + +public class Outer { + public static class Inner {} +} diff --git a/src/it/kotlinInnerClassReference/pom.xml b/src/it/kotlinInnerClassReference/pom.xml new file mode 100644 index 0000000..552992a --- /dev/null +++ b/src/it/kotlinInnerClassReference/pom.xml @@ -0,0 +1,36 @@ + + + + + + 4.0.0 + + org.apache.maven.shared.dependency-analyzer.tests + kotlin-inner-class-reference + 1.0 + pom + + + library + consumer + + diff --git a/src/it/kotlinInnerClassReference/verify.groovy b/src/it/kotlinInnerClassReference/verify.groovy new file mode 100644 index 0000000..b7c782c --- /dev/null +++ b/src/it/kotlinInnerClassReference/verify.groovy @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +def analysis = new File( basedir, 'consumer/target/analysis.txt' ).text + +def expected = ''' +UsedDeclaredArtifacts: + org.apache.maven.shared.dependency-analyzer.tests:nested-class-library:jar:1.0:compile + org.jetbrains.kotlin:kotlin-stdlib:jar:2.4.10:compile + +UsedUndeclaredArtifactsWithClasses: + +UnusedDeclaredArtifacts: + +TestArtifactsWithNonTestScope: +''' + +assert analysis == expected diff --git a/src/it/topLevelDollarClassReference/consumer/pom.xml b/src/it/topLevelDollarClassReference/consumer/pom.xml new file mode 100644 index 0000000..1476bf6 --- /dev/null +++ b/src/it/topLevelDollarClassReference/consumer/pom.xml @@ -0,0 +1,59 @@ + + + + + + 4.0.0 + + + org.apache.maven.shared.dependency-analyzer.tests + top-level-dollar-class-reference + 1.0 + + + dollar-class-consumer + + + + org.apache.maven.shared.dependency-analyzer.tests + dollar-class-library + ${project.version} + + + + + + + org.apache.maven.shared.dependency-analyzer.tests + maven-mock-plugin + 1.0 + + + + mock-analyze + + + + + + + diff --git a/src/it/topLevelDollarClassReference/consumer/src/main/java/consumer/Example.java b/src/it/topLevelDollarClassReference/consumer/src/main/java/consumer/Example.java new file mode 100644 index 0000000..820ae36 --- /dev/null +++ b/src/it/topLevelDollarClassReference/consumer/src/main/java/consumer/Example.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package consumer; + +import library.Dollar$Class; + +public class Example { + public Dollar$Class create() { + return new Dollar$Class(); + } +} diff --git a/src/it/topLevelDollarClassReference/library/pom.xml b/src/it/topLevelDollarClassReference/library/pom.xml new file mode 100644 index 0000000..2050f8a --- /dev/null +++ b/src/it/topLevelDollarClassReference/library/pom.xml @@ -0,0 +1,34 @@ + + + + + + 4.0.0 + + + org.apache.maven.shared.dependency-analyzer.tests + top-level-dollar-class-reference + 1.0 + + + dollar-class-library + diff --git a/src/it/topLevelDollarClassReference/library/src/main/java/library/Dollar$Class.java b/src/it/topLevelDollarClassReference/library/src/main/java/library/Dollar$Class.java new file mode 100644 index 0000000..66d2d7c --- /dev/null +++ b/src/it/topLevelDollarClassReference/library/src/main/java/library/Dollar$Class.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package library; + +public class Dollar$Class {} diff --git a/src/it/topLevelDollarClassReference/pom.xml b/src/it/topLevelDollarClassReference/pom.xml new file mode 100644 index 0000000..8f537e9 --- /dev/null +++ b/src/it/topLevelDollarClassReference/pom.xml @@ -0,0 +1,36 @@ + + + + + + 4.0.0 + + org.apache.maven.shared.dependency-analyzer.tests + top-level-dollar-class-reference + 1.0 + pom + + + library + consumer + + diff --git a/src/it/topLevelDollarClassReference/verify.groovy b/src/it/topLevelDollarClassReference/verify.groovy new file mode 100644 index 0000000..e2bd5a4 --- /dev/null +++ b/src/it/topLevelDollarClassReference/verify.groovy @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +def analysis = new File( basedir, 'consumer/target/analysis.txt' ).text + +def expected = ''' +UsedDeclaredArtifacts: + org.apache.maven.shared.dependency-analyzer.tests:dollar-class-library:jar:1.0:compile + +UsedUndeclaredArtifactsWithClasses: + +UnusedDeclaredArtifacts: + +TestArtifactsWithNonTestScope: +''' + +assert analysis == expected diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java index 4af2ff4..e7b52d3 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java @@ -48,8 +48,8 @@ public CollectorClassFileVisitor(ClassesPatterns excludedClasses) { /** {@inheritDoc} */ @Override public void visitClass(String className, InputStream in) { - // inner classes have equivalent compilation requirement as container class - if (className.indexOf('$') < 0 && !excludedClasses.isMatch(className)) { + // '$' is legal in a top-level class name, so nesting cannot be inferred from the binary name. + if (!excludedClasses.isMatch(className)) { classes.add(className); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java index 65e3937..e416531 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java @@ -108,10 +108,8 @@ void addType(final String usedByClass, final Type t) { * @param name a {@link java.lang.String} object. */ public void add(final String usedByClass, final String name) { - // inner classes have equivalent compilation requirement as container class - if (name.indexOf('$') < 0) { - classUsages.add(new DependencyUsage(name, usedByClass)); - } + // '$' is legal in a top-level class name, so nesting cannot be inferred from the binary name. + classUsages.add(new DependencyUsage(name, usedByClass)); } void addNames(final String usedByClass, final String[] names) { diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java index 9ae1faf..015dfc8 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java @@ -51,4 +51,12 @@ void testVisitClass() { assertThat(visitor.getClasses()).isEqualTo(expected); } + + @Test + void testVisitClassesContainingDollar() { + visitor.visitClass("a.b.Outer$Inner", null); + visitor.visitClass("x.y.Outer$Inner$Nested", null); + + assertThat(visitor.getClasses()).containsExactlyInAnyOrder("a.b.Outer$Inner", "x.y.Outer$Inner$Nested"); + } } diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java index 5edb864..96f9ab9 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.jar.JarOutputStream; @@ -91,6 +92,20 @@ void testAnalyzeBadJar() throws IOException { } } + @Test + void testAnalyzeDirectoryPreservesDollarClassNames() throws IOException { + Path directory = Files.createDirectories(tempDir.resolve("classes/library")); + Files.write(directory.resolve("Dollar$Class.class"), new byte[0]); + Files.write(directory.resolve("Outer$Inner.class"), new byte[0]); + + DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer(); + Set actualClasses = analyzer.analyze( + tempDir.resolve("classes").toUri().toURL(), + new ClassesPatterns(Collections.singleton("library\\.Outer\\$Inner"))); + + assertThat(actualClasses).containsExactly("library.Dollar$Class"); + } + private void addZipEntry(JarOutputStream out, String fileName, String content) throws IOException { out.putNextEntry(new ZipEntry(fileName)); byte[] bytes = content.getBytes(StandardCharsets.UTF_8); diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java index 562ab39..0fb7bd8 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java @@ -18,6 +18,7 @@ */ package org.apache.maven.shared.dependency.analyzer.asm; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -31,6 +32,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -114,11 +118,30 @@ void testNoMethodHandle() throws IOException { } @Test - void testInnerClassAsContainer() throws IOException { + void testInnerClassNamePreserved() throws IOException { Set dependencies = getDependencies(InnerClassCase.class); - for (String dependency : dependencies) { - assertThat(dependency).doesNotContain("$"); - } - assertThat(dependencies).contains("java.lang.System"); + assertThat(dependencies).contains("java.lang.System", "java.lang.invoke.MethodHandles$Lookup"); + } + + @Test + void testInnerClassWithoutContainerClassInConstantPool() { + ClassWriter writer = new ClassWriter(0); + writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "consumer/Example", null, "java/lang/Object", null); + + MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, "use", "()V", null, null); + method.visitCode(); + method.visitTypeInsn(Opcodes.NEW, "dependency/Outer$Inner"); + method.visitInsn(Opcodes.DUP); + method.visitMethodInsn(Opcodes.INVOKESPECIAL, "dependency/Outer$Inner", "", "()V", false); + method.visitInsn(Opcodes.POP); + method.visitInsn(Opcodes.RETURN); + method.visitMaxs(2, 1); + method.visitEnd(); + writer.visitEnd(); + + DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); + visitor.visitClass("consumer.Example", new ByteArrayInputStream(writer.toByteArray())); + + assertThat(visitor.getDependencies()).contains("dependency.Outer$Inner").doesNotContain("dependency.Outer"); } }