diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ab7479d..08cd9ce7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,13 +20,9 @@ updates: directory: "/" schedule: interval: daily - time: '04:00' open-pull-requests-limit: 10 - ignore: - - dependency-name: org.apache.maven:* - versions: - - "> 3.1.1" - # Ignore Maven 3.2.1+ - - dependency-name: org.apache.maven.plugin-testing:maven-plugin-testing-tools - versions: - - ">=3.2.0" \ No newline at end of file + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml new file mode 100644 index 00000000..227685e6 --- /dev/null +++ b/.github/workflows/maven-verify.yml @@ -0,0 +1,26 @@ +# 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. +name: Java CI + +on: + push: + pull_request: + +jobs: + build: + name: Verify + uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v4 diff --git a/pom.xml b/pom.xml index 37aa8c85..6c890a3b 100644 --- a/pom.xml +++ b/pom.xml @@ -24,12 +24,12 @@ org.apache.maven.shared maven-shared-components - 39 + 42 maven-dependency-analyzer - 1.13.3-SNAPSHOT + 1.14.0 jar Apache Maven Dependency Analyzer @@ -38,7 +38,7 @@ scm:git:https://gitbox.apache.org/repos/asf/maven-dependency-analyzer.git scm:git:https://gitbox.apache.org/repos/asf/maven-dependency-analyzer.git - HEAD + maven-dependency-analyzer-1.14.0 https://github.com/apache/maven-dependency-analyzer/tree/${project.scm.tag} @@ -57,73 +57,70 @@ - 3.2.5 - 1.7.36 + 3.9.6 8 - 2023-04-30T22:19:57Z - - 3.8.1 + 2024-05-12T20:32:57Z + + + + org.assertj + assertj-bom + 3.25.3 + pom + import + + + org.apache.maven maven-core ${mavenVersion} + provided org.apache.maven maven-artifact ${mavenVersion} + provided org.apache.maven maven-model ${mavenVersion} + provided javax.inject javax.inject 1 + provided org.ow2.asm asm - 9.5 - - - - org.slf4j - slf4j-api - ${slf4jVersion} - - - - commons-io - commons-io - 2.13.0 + 9.7 org.junit.jupiter junit-jupiter-api - 5.10.0 test - org.assertj - assertj-core - 3.24.2 + org.junit.jupiter + junit-jupiter-params test - org.slf4j - slf4j-simple - ${slf4jVersion} + org.assertj + assertj-core test @@ -157,13 +154,12 @@ org.apache.maven.plugins maven-invoker-plugin - - 3.5.1 ${project.build.directory}/it target/local-repo src/it/settings.xml verify + setup verify diff --git a/src/it/excludeClassFromJar/pom.xml b/src/it/excludeClassFromJar/pom.xml new file mode 100644 index 00000000..7165d0a5 --- /dev/null +++ b/src/it/excludeClassFromJar/pom.xml @@ -0,0 +1,64 @@ + + + + + + 4.0.0 + + org.apache.maven.shared.dependency-analyzer.tests + jarWithXercesDependencies + 1.0 + jar + + + + dom4j + dom4j + 1.6.1 + + + + + + + org.apache.maven.shared.dependency-analyzer.tests + maven-mock-plugin + 1.0 + + + + mock-analyze + + + + + + org.xml.sax.* + + + + + + + diff --git a/src/it/excludeClassFromJar/src/main/java/jarWithXmlTransitiveDependency/Project.java b/src/it/excludeClassFromJar/src/main/java/jarWithXmlTransitiveDependency/Project.java new file mode 100644 index 00000000..35cd164e --- /dev/null +++ b/src/it/excludeClassFromJar/src/main/java/jarWithXmlTransitiveDependency/Project.java @@ -0,0 +1,42 @@ +package jarWithXmlTransitiveDependency; + +/* + * 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. + */ + +import org.dom4j.Text; +import org.xml.sax.Parser; + +/** + * Dependency on dom4j gives xml-apis transitive dependency, which contains SAX Parser class. But SAX Parser is available in + * JDK: no need to declare a direct dependency. + * + */ +public class Project +{ + public Text text; + + public Parser parser; + + // constructors ----------------------------------------------------------- + + public Project() + { + // no op + } +} diff --git a/src/it/excludeClassFromJar/verify.groovy b/src/it/excludeClassFromJar/verify.groovy new file mode 100644 index 00000000..088383c2 --- /dev/null +++ b/src/it/excludeClassFromJar/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, 'target/analysis.txt' ).text + +def expected = ''' +UsedDeclaredArtifacts: + dom4j:dom4j:jar:1.6.1:compile + +UsedUndeclaredArtifactsWithClasses: + +UnusedDeclaredArtifacts: + +TestArtifactsWithNonTestScope: +''' + +assert analysis == expected diff --git a/src/it/excludeClassFromProject/pom.xml b/src/it/excludeClassFromProject/pom.xml new file mode 100644 index 00000000..38857612 --- /dev/null +++ b/src/it/excludeClassFromProject/pom.xml @@ -0,0 +1,56 @@ + + + + + + 4.0.0 + + org.apache.maven.shared.dependency-analyzer.tests + jarWithXercesDependencies + 1.0 + jar + + + + + org.apache.maven.shared.dependency-analyzer.tests + maven-mock-plugin + 1.0 + + + + mock-analyze + + + + + + org.example.BadClass + + + + + + + diff --git a/src/it/excludeClassFromProject/setup.groovy b/src/it/excludeClassFromProject/setup.groovy new file mode 100644 index 00000000..f6eec9b6 --- /dev/null +++ b/src/it/excludeClassFromProject/setup.groovy @@ -0,0 +1,24 @@ +/* + * 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 badClass = new File(basedir, 'target/classes/org/example/BadClass.class') + +badClass.getParentFile().mkdirs() +badClass << 'some content' + +assert badClass.isFile() diff --git a/src/it/excludeClassFromProject/verify.groovy b/src/it/excludeClassFromProject/verify.groovy new file mode 100644 index 00000000..e5458570 --- /dev/null +++ b/src/it/excludeClassFromProject/verify.groovy @@ -0,0 +1,32 @@ +/* + * 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, 'target/analysis.txt' ).text + +def expected = ''' +UsedDeclaredArtifacts: + +UsedUndeclaredArtifactsWithClasses: + +UnusedDeclaredArtifacts: + +TestArtifactsWithNonTestScope: +''' + +assert analysis == expected diff --git a/src/it/jarWithCompileDependency/project2/pom.xml b/src/it/jarWithCompileDependency/project2/pom.xml index 08dcea7f..e1eb8893 100644 --- a/src/it/jarWithCompileDependency/project2/pom.xml +++ b/src/it/jarWithCompileDependency/project2/pom.xml @@ -39,7 +39,7 @@ com.google.guava guava - 30.1.1-android + 32.0.0-android diff --git a/src/it/jarWithCompileDependency/verify.groovy b/src/it/jarWithCompileDependency/verify.groovy index d31d9cdd..7b845723 100644 --- a/src/it/jarWithCompileDependency/verify.groovy +++ b/src/it/jarWithCompileDependency/verify.groovy @@ -22,7 +22,7 @@ def analysis = new File( basedir, 'project2/target/analysis.txt' ).text def expected = ''' UsedDeclaredArtifacts: org.apache.maven.shared.dependency-analyzer.tests:jarWithCompileDependency1:jar:1.0:compile - com.google.guava:guava:jar:30.1.1-android:compile + com.google.guava:guava:jar:32.0.0-android:compile UsedUndeclaredArtifactsWithClasses: diff --git a/src/it/setup-mock-plugin/pom.xml b/src/it/setup-mock-plugin/pom.xml index 509185e2..83f50eb0 100644 --- a/src/it/setup-mock-plugin/pom.xml +++ b/src/it/setup-mock-plugin/pom.xml @@ -49,7 +49,7 @@ org.apache.maven.plugin-tools maven-plugin-tools-annotations - @maven.plugin.tools.version@ + @version.maven-plugin-tools@ provided @@ -65,7 +65,7 @@ org.apache.maven.plugins maven-plugin-plugin - @maven.plugin.tools.version@ + @version.maven-plugin-tools@ diff --git a/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java b/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java index 8d9fe65c..27d78eec 100644 --- a/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java +++ b/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java @@ -24,6 +24,7 @@ import java.io.FileNotFoundException; import java.io.PrintWriter; import java.nio.file.Files; +import java.util.Set; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -63,12 +64,15 @@ public void println() @Parameter( defaultValue = "${project.build.directory}/analysis.txt", readonly = true ) private File output; + @Parameter + private Set excludedClasses; + @Override public void execute() throws MojoExecutionException, MojoFailureException { try { - ProjectDependencyAnalysis analysis = analyzer.analyze( project ); + ProjectDependencyAnalysis analysis = analyzer.analyze( project, excludedClasses ); Files.createDirectories( output.toPath().getParent() ); try ( PrintWriter printWriter = new UnixPrintWiter( output ) ) diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java index 679b4d62..af2b154b 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java @@ -36,5 +36,16 @@ public interface ClassAnalyzer { * @return a {@link java.util.Set} object * @throws java.io.IOException if any */ - Set analyze(URL url) throws IOException; + default Set analyze(URL url) throws IOException { + return analyze(url, new ClassesPatterns()); + } + + /** + *

analyze.

+ * + * @param url the JAR file or directory to analyze + * @return a {@link java.util.Set} object + * @throws java.io.IOException if any + */ + Set analyze(URL url, ClassesPatterns excludedClasses) throws IOException; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassesPatterns.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassesPatterns.java new file mode 100644 index 00000000..ce699307 --- /dev/null +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassesPatterns.java @@ -0,0 +1,56 @@ +/* + * 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 org.apache.maven.shared.dependency.analyzer; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Patterns for classes + */ +public class ClassesPatterns { + + private final Collection patterns; + + /** + * Default constructor. + * + * @param patterns a patterns to mach + */ + public ClassesPatterns(Collection patterns) { + if (patterns == null) { + this.patterns = Collections.emptyList(); + } else { + this.patterns = patterns.stream().map(Pattern::compile).collect(Collectors.toSet()); + } + } + + public ClassesPatterns() { + this.patterns = Collections.emptySet(); + } + + public boolean isMatch(String className) { + if (patterns.isEmpty()) { + return false; + } + return patterns.stream().anyMatch(pattern -> pattern.matcher(className).matches()); + } +} 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 4adf2d23..4af2ff4e 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 @@ -31,18 +31,25 @@ public class CollectorClassFileVisitor implements ClassFileVisitor { private final Set classes; + private final ClassesPatterns excludedClasses; + /** *

Constructor for CollectorClassFileVisitor.

*/ public CollectorClassFileVisitor() { + this(new ClassesPatterns()); + } + + public CollectorClassFileVisitor(ClassesPatterns excludedClasses) { classes = new HashSet<>(); + this.excludedClasses = excludedClasses; } /** {@inheritDoc} */ @Override public void visitClass(String className, InputStream in) { // inner classes have equivalent compilation requirement as container class - if (className.indexOf('$') < 0) { + if (className.indexOf('$') < 0 && !excludedClasses.isMatch(className)) { classes.add(className); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java index dfbed05e..43022799 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java @@ -35,10 +35,9 @@ @Singleton public class DefaultClassAnalyzer implements ClassAnalyzer { - /** {@inheritDoc} */ @Override - public Set analyze(URL url) throws IOException { - CollectorClassFileVisitor visitor = new CollectorClassFileVisitor(); + public Set analyze(URL url, ClassesPatterns excludedClasses) throws IOException { + CollectorClassFileVisitor visitor = new CollectorClassFileVisitor(excludedClasses); try { ClassFileVisitorUtils.accept(url, visitor); diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java index 39bdcbf6..3887187b 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.util.Collection; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -57,12 +58,14 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz * {@inheritDoc} */ @Override - public ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException { + public ProjectDependencyAnalysis analyze(MavenProject project, Collection excludedClasses) + throws ProjectDependencyAnalyzerException { try { - Map> artifactClassMap = buildArtifactClassMap(project); + ClassesPatterns excludedClassesPatterns = new ClassesPatterns(excludedClasses); + Map> artifactClassMap = buildArtifactClassMap(project, excludedClassesPatterns); - Set mainDependencyClasses = buildMainDependencyClasses(project); - Set testDependencyClasses = buildTestDependencyClasses(project); + Set mainDependencyClasses = buildMainDependencyClasses(project, excludedClassesPatterns); + Set testDependencyClasses = buildTestDependencyClasses(project, excludedClassesPatterns); Set dependencyClasses = new HashSet<>(); dependencyClasses.addAll(mainDependencyClasses); @@ -144,7 +147,8 @@ private static Set getTestArtifactsWithNonTestScope(Set test return nonTestScopeArtifacts; } - protected Map> buildArtifactClassMap(MavenProject project) throws IOException { + protected Map> buildArtifactClassMap(MavenProject project, ClassesPatterns excludedClasses) + throws IOException { Map> artifactClassMap = new LinkedHashMap<>(); Set dependencyArtifacts = project.getArtifacts(); @@ -165,7 +169,9 @@ protected Map> buildArtifactClassMap(MavenProject project) if (entry.endsWith(".class")) { String className = entry.replace('/', '.'); className = className.substring(0, className.length() - ".class".length()); - classes.add(className); + if (!excludedClasses.isMatch(className)) { + classes.add(className); + } } } @@ -173,7 +179,7 @@ protected Map> buildArtifactClassMap(MavenProject project) } } else if (file != null && file.isDirectory()) { URL url = file.toURI().toURL(); - Set classes = classAnalyzer.analyze(url); + Set classes = classAnalyzer.analyze(url, excludedClasses); artifactClassMap.put(artifact, classes); } @@ -192,20 +198,22 @@ private static Set buildTestOnlyDependencyClasses( return testOnlyDependencyClasses; } - private Set buildMainDependencyClasses(MavenProject project) throws IOException { + private Set buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses) + throws IOException { String outputDirectory = project.getBuild().getOutputDirectory(); - return buildDependencyClasses(outputDirectory); + return buildDependencyClasses(outputDirectory, excludedClasses); } - private Set buildTestDependencyClasses(MavenProject project) throws IOException { + private Set buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses) + throws IOException { String testOutputDirectory = project.getBuild().getTestOutputDirectory(); - return buildDependencyClasses(testOutputDirectory); + return buildDependencyClasses(testOutputDirectory, excludedClasses); } - private Set buildDependencyClasses(String path) throws IOException { + private Set buildDependencyClasses(String path, ClassesPatterns excludedClasses) throws IOException { URL url = new File(path).toURI().toURL(); - return dependencyAnalyzer.analyzeUsages(url); + return dependencyAnalyzer.analyzeUsages(url, excludedClasses); } private static Set buildDeclaredArtifacts(MavenProject project) { diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java index 7e7fa399..2fb94053 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java @@ -33,12 +33,24 @@ public interface DependencyAnalyzer { /** *

analyze.

* - * @param url the JAR file or directory to analyze + * @param url the JAR file or directory to analyze * @return the set of class names referenced by the library * @throws IOException if an error occurs reading a JAR or .class file */ default Set analyze(URL url) throws IOException { - return analyzeUsages(url).stream() + return analyze(url, new ClassesPatterns()); + } + + /** + *

analyze.

+ * + * @param url the JAR file or directory to analyze + * @param excludeClasses a class list to exclude + * @return the set of class names referenced by the library + * @throws IOException if an error occurs reading a JAR or .class file + */ + default Set analyze(URL url, ClassesPatterns excludeClasses) throws IOException { + return analyzeUsages(url, excludeClasses).stream() .map(DependencyUsage::getDependencyClass) .collect(Collectors.toSet()); } @@ -51,5 +63,6 @@ default Set analyze(URL url) throws IOException { * classes declaring those references. * @throws IOException if an error occurs reading a JAR or .class file */ - Set analyzeUsages(URL url) throws IOException; + Set analyzeUsages(URL url, ClassesPatterns excludeClasses) throws IOException; + } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java index 9503ea3d..4b0d84ac 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java @@ -18,6 +18,8 @@ */ package org.apache.maven.shared.dependency.analyzer; +import java.util.Collection; + import org.apache.maven.project.MavenProject; /** @@ -32,12 +34,26 @@ * @author Mark Hobson */ public interface ProjectDependencyAnalyzer { + + /** + *

analyze.

+ * + * @param project a {@link org.apache.maven.project.MavenProject} object + * @return a {@link org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis} object + * @throws org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException if any + */ + default ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException { + return analyze(project, null); + } + /** *

analyze.

* * @param project a {@link org.apache.maven.project.MavenProject} object + * @param excludedClasses collection of regular expression of classes name to exclude * @return a {@link org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis} object * @throws org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException if any */ - ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException; + ProjectDependencyAnalysis analyze(MavenProject project, Collection excludedClasses) + throws ProjectDependencyAnalyzerException; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java index 8916cbad..4e85c7b8 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java @@ -26,6 +26,7 @@ import java.util.Set; import org.apache.maven.shared.dependency.analyzer.ClassFileVisitorUtils; +import org.apache.maven.shared.dependency.analyzer.ClassesPatterns; import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer; import org.apache.maven.shared.dependency.analyzer.DependencyUsage; @@ -39,8 +40,8 @@ public class ASMDependencyAnalyzer implements DependencyAnalyzer { @Override - public Set analyzeUsages(URL url) throws IOException { - DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); + public Set analyzeUsages(URL url, ClassesPatterns excludeClasses) throws IOException { + DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(excludeClasses); ClassFileVisitorUtils.accept(url, visitor); diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java index e567e578..9daed3a8 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java @@ -18,10 +18,18 @@ */ package org.apache.maven.shared.dependency.analyzer.asm; -import org.objectweb.asm.*; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.TypePath; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; +import java.util.Arrays; + /** * Computes the set of classes referenced by visited code. * Inspired by org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. @@ -180,4 +188,14 @@ private void addTypeSignature(final String signature) { new SignatureReader(signature).acceptType(signatureVisitor); } } + + @Override + public void visitInvokeDynamicInsn( + String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { + Arrays.stream(bootstrapMethodArguments) + .filter(Type.class::isInstance) + .map(Type.class::cast) + .map(Type::getDescriptor) + .forEach(desc -> resultCollector.addMethodDesc(usedByClass, desc)); + } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java index b37fea6c..7516381e 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java @@ -18,17 +18,17 @@ */ package org.apache.maven.shared.dependency.analyzer.asm; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.util.Set; -import org.apache.commons.io.IOUtils; import org.apache.maven.shared.dependency.analyzer.ClassFileVisitor; +import org.apache.maven.shared.dependency.analyzer.ClassesPatterns; import org.apache.maven.shared.dependency.analyzer.DependencyUsage; import org.objectweb.asm.*; import org.objectweb.asm.signature.SignatureVisitor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Computes the set of classes referenced by visited class files, using @@ -38,14 +38,26 @@ * @see #getDependencies() */ public class DependencyClassFileVisitor implements ClassFileVisitor { + private static final int BUF_SIZE = 8192; + private final ResultCollector resultCollector = new ResultCollector(); - private final Logger logger = LoggerFactory.getLogger(getClass()); + private final ClassesPatterns excludedClasses; + + /** + *

Constructor for DependencyClassFileVisitor.

+ */ + public DependencyClassFileVisitor(ClassesPatterns excludedClasses) { + + this.excludedClasses = excludedClasses; + } /** *

Constructor for DependencyClassFileVisitor.

*/ - public DependencyClassFileVisitor() {} + public DependencyClassFileVisitor() { + this(new ClassesPatterns()); + } /** * {@inheritDoc} @@ -53,7 +65,12 @@ public DependencyClassFileVisitor() {} @Override public void visitClass(String className, InputStream in) { try { - byte[] byteCode = IOUtils.toByteArray(in); + byte[] byteCode = toByteArray(in); + + if (excludedClasses.isMatch(className)) { + return; + } + ClassReader reader = new ClassReader(byteCode); final Set constantPoolClassRefs = ConstantPoolParser.getConstantPoolClassReferences(byteCode); @@ -71,15 +88,24 @@ public void visitClass(String className, InputStream in) { reader.accept(classVisitor, 0); } catch (IOException exception) { - exception.printStackTrace(); + throw new UncheckedIOException(exception); } catch (IndexOutOfBoundsException e) { - // some bug inside ASM causes an IOB exception. Log it and move on? + // some bug inside ASM causes an IOB exception. // this happens when the class isn't valid. - logger.warn("Unable to process: " + className, e); + throw new VisitClassException("Unable to process: " + className, e); } catch (IllegalArgumentException e) { - // [MSHARED-1248] should log instead of failing when analyzing a corrupted jar file - logger.warn("Byte code of '" + className + "' is corrupt", e); + throw new VisitClassException("Byte code of '" + className + "' is corrupt", e); + } + } + + private byte[] toByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[BUF_SIZE]; + int i; + while ((i = in.read(buffer)) > 0) { + out.write(buffer, 0, i); } + return out.toByteArray(); } /** diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/VisitClassException.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/VisitClassException.java new file mode 100644 index 00000000..d68bf99c --- /dev/null +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/VisitClassException.java @@ -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. + */ +package org.apache.maven.shared.dependency.analyzer.asm; + +/** + * Exception for processing class. + */ +public class VisitClassException extends RuntimeException { + /** + * A constructor + * @param message message + * @param cause cause of exception + */ + public VisitClassException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassesPatternsTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassesPatternsTest.java new file mode 100644 index 00000000..19f6d4cc --- /dev/null +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassesPatternsTest.java @@ -0,0 +1,51 @@ +/* + * 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 org.apache.maven.shared.dependency.analyzer; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ClassesPatternsTest { + + @Test + void classPatternsTest() { + ClassesPatterns classesPatterns = new ClassesPatterns(Arrays.asList("Test1.*", "io.example.test.Test2")); + + assertTrue(classesPatterns.isMatch("Test1.Test2")); + assertFalse(classesPatterns.isMatch("Test2.Test2")); + assertTrue(classesPatterns.isMatch("io.example.test.Test2")); + } + + @Test + void emptyClassPatternsTest() { + ClassesPatterns classesPatterns = new ClassesPatterns(); + + assertFalse(classesPatterns.isMatch("Test")); + } + + @Test + void nullClassPatternsTest() { + ClassesPatterns classesPatterns = new ClassesPatterns(null); + + assertFalse(classesPatterns.isMatch("Test")); + } +} diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java index cecf6859..30d19c56 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java @@ -18,9 +18,14 @@ */ package org.apache.maven.shared.dependency.analyzer.asm; +import java.io.IOException; import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; import java.util.Set; +import org.apache.maven.shared.dependency.analyzer.ClassesPatterns; import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer; import org.junit.jupiter.api.Test; @@ -40,4 +45,25 @@ void test() throws Exception { assertThat(result).isNotEmpty(); } + + @Test + void verify_implicit_class_reference_included_in_used_classes() throws IOException { + Path file = Paths.get("target/test-classes/org/apache/maven/shared/dependency/analyzer/testcases/analyze"); + + Set result = analyzer.analyze(file.toUri().toURL()); + assertThat(result).contains("org.apache.maven.artifact.resolver.ArtifactResolutionRequest"); + assertThat(result).contains("java.util.regex.Pattern"); + } + + @Test + void verify_excluded_classes() throws IOException { + Path file = Paths.get("target/test-classes/org/apache/maven/shared/dependency/analyzer/testcases/analyze"); + + Set result = + analyzer.analyze(file.toUri().toURL(), new ClassesPatterns(Collections.singleton("ClassToExclude"))); + assertThat(result).contains("org.apache.maven.artifact.resolver.ArtifactResolutionRequest"); + assertThat(result).doesNotContain("java.util.regex.Pattern"); + assertThat(result) + .doesNotContain("org.apache.maven.shared.dependency.analyzer.testcases.analyze.ClassToExclude"); + } } diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java index ce23138d..0ed87b77 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java @@ -673,6 +673,14 @@ void testVisitMaxs() { assertThat(resultCollector.getDependencies()).isEmpty(); } + // visitInvokeDynamicInsn tests ------------------------------------------- + @Test + void testVisitInvokeDynamic() { + Type type = Type.getType("(La/b/C;)V"); + mv.visitInvokeDynamicInsn("a", "", null, type); + assertThat(resultCollector.getDependencies()).contains("a.b.C"); + } + private void assertVisitor(Object actualVisitor) { // assertEquals( visitor, actualVisitor ); } 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 2590bbcc..7d8dc86f 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 @@ -29,8 +29,11 @@ import org.apache.maven.shared.dependency.analyzer.testcases.InnerClassCase; import org.apache.maven.shared.dependency.analyzer.testcases.MethodHandleCases; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; class ResultCollectorTest { @@ -48,7 +51,6 @@ Set getDependencies(Class inspectClass) throws IOException { @Test void testJava11Invoke() throws IOException { - String className = "issue362.Bcel362"; Path path = Paths.get( "src/test/resources/org/apache/maven/shared/dependency/analyzer/commons-bcel-issue362/Bcel362.classx"); DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); @@ -57,50 +59,23 @@ void testJava11Invoke() throws IOException { } } - @Test - public void testOssFuzz51980() throws IOException { + @ParameterizedTest + @ValueSource( + strings = { + "issue51980", + "issue51989", + "issue52168", + "issue53543", + "issue53544a", + "issue53620", + "issue53676", + "issue54119", + "issue54254" + }) + void testOssFuzz(String name) { // Add a non-"class" suffix so that surefire does not try to read the file and fail the build - visitClass(ROOT + "/ossfuzz/issue51980/Test.class.clazz"); - } - - @Test - public void testOssFuzz51989() throws IOException { - visitClass(ROOT + "/ossfuzz/issue51989/Test.class.clazz"); - } - - @Test - public void testOssFuzz52168() throws IOException { - visitClass(ROOT + "/ossfuzz/issue52168/Test.class.clazz"); - } - - @Test - public void testOssFuzz53543() throws IOException { - visitClass(ROOT + "/ossfuzz/issue53543/Test.class.clazz"); - } - - @Test - public void testOssFuzz53544a() throws IOException { - visitClass(ROOT + "/ossfuzz/issue53544a/Test.class.clazz"); - } - - @Test - public void testOssFuzz53620() throws IOException { - visitClass(ROOT + "/ossfuzz/issue53620/Test.class.clazz"); - } - - @Test - public void testOssFuzz53676() throws IOException { - visitClass(ROOT + "/ossfuzz/issue53676/Test.class.clazz"); - } - - @Test - public void testOssFuzz54199() throws IOException { - visitClass(ROOT + "/ossfuzz/issue54119/Test.class.clazz"); - } - - @Test - public void testOssFuzz54254() throws IOException { - visitClass(ROOT + "/ossfuzz/issue54254/Test.class.clazz"); + assertThatCode(() -> visitClass(ROOT + "/ossfuzz/" + name + "/Test.class.clazz")) + .isExactlyInstanceOf(VisitClassException.class); } private void visitClass(String location) throws IOException { diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/analyze/AnalyzedClass.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/analyze/AnalyzedClass.java new file mode 100644 index 00000000..b77c4628 --- /dev/null +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/analyze/AnalyzedClass.java @@ -0,0 +1,36 @@ +/* + * 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 org.apache.maven.shared.dependency.analyzer.testcases.analyze; + +import org.apache.maven.shared.dependency.analyzer.testcases.prepare.Prepare; + +/** + * Class to be analyzed in unit test. + *

+ * The handler method in {@link Prepare} takes an implicit Consumer<ArtifactResolutionRequest> argument. Analyze + * this class to verify the implicit reference. + */ +public class AnalyzedClass { + + public void useFirst() { + new Prepare().handler(this::doNothing); + } + + private void doNothing(final Object object) {} +} diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/analyze/ClassToExclude.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/analyze/ClassToExclude.java new file mode 100644 index 00000000..bf1771de --- /dev/null +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/analyze/ClassToExclude.java @@ -0,0 +1,28 @@ +/* + * 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 org.apache.maven.shared.dependency.analyzer.testcases.analyze; + +import java.util.regex.Pattern; + +/** + * Class to be skipped during analyzed in unit test. + */ +public class ClassToExclude { + private void doNothing(final Pattern pattern) {} +} diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/prepare/Prepare.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/prepare/Prepare.java new file mode 100644 index 00000000..2a6a304f --- /dev/null +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/testcases/prepare/Prepare.java @@ -0,0 +1,36 @@ +/* + * 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 org.apache.maven.shared.dependency.analyzer.testcases.prepare; + +import java.util.function.Consumer; + +import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; +import org.apache.maven.artifact.resolver.ResolutionErrorHandler; + +/** + * Class to be used for verifying that analyzer picks up usage of classes with no import. + */ +public class Prepare { + + public void handler(Consumer consumer) { + ResolutionErrorHandler resolutionErrorHandler = (request, result) -> { + consumer.accept(request); + }; + } +}