-
Notifications
You must be signed in to change notification settings - Fork 182
Follow up of 459 #1536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rmannibucau
wants to merge
8
commits into
master
Choose a base branch
from
pr-459
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+627
−201
Open
Follow up of 459 #1536
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
27c871b
Allowlist org.glassfish:javax.json
elharo 4d98098
integration test
elharo b4a2465
comments
elharo c703261
spotless
elharo d9b1e58
fix it
elharo f4fd3bd
some draft of a more generic detection of used _unused_ dependencies
rmannibucau 36b9def
rebase
rmannibucau 800ac56
fix deps
rmannibucau File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,15 +19,23 @@ | |
package org.apache.maven.plugins.dependency.analyze; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.StringWriter; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.HashSet; | ||
import java.util.Iterator; | ||
import java.util.LinkedHashMap; | ||
import java.util.LinkedHashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.jar.JarEntry; | ||
import java.util.jar.JarFile; | ||
import java.util.stream.Stream; | ||
|
||
import org.apache.maven.artifact.Artifact; | ||
import org.apache.maven.artifact.resolver.filter.ArtifactFilter; | ||
|
@@ -43,6 +51,17 @@ | |
import org.codehaus.plexus.PlexusContainer; | ||
import org.codehaus.plexus.component.repository.exception.ComponentLookupException; | ||
import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; | ||
import org.objectweb.asm.ClassReader; | ||
import org.objectweb.asm.ClassVisitor; | ||
import org.objectweb.asm.MethodVisitor; | ||
import org.objectweb.asm.ModuleVisitor; | ||
import org.objectweb.asm.Type; | ||
|
||
import static java.util.Collections.list; | ||
import static java.util.stream.Collectors.toList; | ||
import static java.util.stream.Collectors.toSet; | ||
import static org.objectweb.asm.Opcodes.ASM9; | ||
import static org.objectweb.asm.Opcodes.INVOKESTATIC; | ||
|
||
/** | ||
* Analyzes the dependencies of this project and determines which are: used and declared; used and undeclared; unused | ||
|
@@ -214,16 +233,11 @@ public abstract class AbstractAnalyzeMojo extends AbstractMojo { | |
* <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> matches all snapshot artifacts. | ||
* </p> | ||
* | ||
* <p>Certain dependencies that are known to be used and loaded by reflection | ||
* are always ignored. This includes {@code org.slf4j:slf4j-simple::}.</p> | ||
* | ||
* @since 2.10 | ||
*/ | ||
@Parameter | ||
private String[] ignoredUnusedDeclaredDependencies = new String[0]; | ||
|
||
private String[] unconditionallyIgnoredDeclaredDependencies = {"org.slf4j:slf4j-simple::"}; | ||
|
||
/** | ||
* List of dependencies that are ignored if they are in not test scope but are only used in test classes. | ||
* The filter syntax is: | ||
|
@@ -241,7 +255,7 @@ public abstract class AbstractAnalyzeMojo extends AbstractMojo { | |
* | ||
* @since 3.3.0 | ||
*/ | ||
@Parameter(defaultValue = "org.slf4j:slf4j-simple::") | ||
@Parameter | ||
private String[] ignoredNonTestScopedDependencies; | ||
|
||
/** | ||
|
@@ -366,7 +380,6 @@ private boolean checkDependencies() throws MojoExecutionException { | |
|
||
ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, ignoredDependencies)); | ||
ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, ignoredUnusedDeclaredDependencies)); | ||
ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, unconditionallyIgnoredDeclaredDependencies)); | ||
|
||
if (ignoreAllNonTestScoped) { | ||
ignoredNonTestScope.addAll(filterDependencies(nonTestScope, new String[] {"*"})); | ||
|
@@ -398,11 +411,15 @@ private boolean checkDependencies() throws MojoExecutionException { | |
} | ||
|
||
if (!unusedDeclared.isEmpty()) { | ||
logDependencyWarning("Unused declared dependencies found:"); | ||
|
||
logArtifacts(unusedDeclared, true); | ||
reported = true; | ||
warning = true; | ||
final Set<String> declaredSpi = scanForSpiUsage(usedDeclared, usedUndeclaredWithClasses); | ||
cleanupUnused(declaredSpi, unusedDeclared); | ||
if (!unusedDeclared.isEmpty()) { | ||
logDependencyWarning("Unused declared dependencies found:"); | ||
|
||
logArtifacts(unusedDeclared, true); | ||
reported = true; | ||
warning = true; | ||
} | ||
} | ||
|
||
if (!nonTestScope.isEmpty()) { | ||
|
@@ -449,6 +466,125 @@ private boolean checkDependencies() throws MojoExecutionException { | |
return warning; | ||
} | ||
|
||
// todo: enhance analyzer (dependency) to do it since it already visits classes | ||
// will save some time | ||
private Set<String> scanForSpiUsage( | ||
final Set<Artifact> usedDeclared, final Map<Artifact, Set<String>> usedUndeclaredWithClasses) { | ||
return Stream.concat( | ||
usedDeclared.stream().flatMap(this::findUsedSpi), | ||
usedUndeclaredWithClasses.keySet().stream().flatMap(this::findUsedSpi)) | ||
.collect(toSet()); | ||
} | ||
|
||
private Stream<String> findUsedSpi(final Artifact artifact) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we extends a maven-dependency-analyzer with such feature? |
||
try (JarFile jar = new JarFile(artifact.getFile())) { | ||
return list(jar.entries()).stream() | ||
.filter(entry -> entry.getName().endsWith(".class")) | ||
.flatMap(entry -> { | ||
final ClassReader classReader; | ||
try { | ||
classReader = new ClassReader(jar.getInputStream(entry)); | ||
} catch (IOException e) { | ||
return Stream.empty(); | ||
} | ||
final Set<String> spi = new HashSet<>(); | ||
classReader.accept( | ||
new ClassVisitor(ASM9) { | ||
@Override | ||
public MethodVisitor visitMethod( | ||
final int access, | ||
final String name, | ||
final String descriptor, | ||
final String signature, | ||
final String[] exceptions) { | ||
return new MethodVisitor(ASM9) { | ||
private Type lastType = null; | ||
|
||
@Override | ||
public void visitLdcInsn(final Object value) { | ||
if (value instanceof Type) { | ||
lastType = (Type) value; | ||
} | ||
} | ||
|
||
@Override | ||
public void visitMethodInsn( | ||
final int opcode, | ||
final String owner, | ||
final String name, | ||
final String descriptor, | ||
final boolean isInterface) { | ||
if (opcode == INVOKESTATIC | ||
&& Objects.equals(owner, "java/util/ServiceLoader") | ||
&& Objects.equals(name, "load")) { | ||
spi.add(lastType.getClassName()); | ||
} | ||
lastType = null; | ||
} | ||
}; | ||
} | ||
}, | ||
0); | ||
return spi.stream(); | ||
}) | ||
.collect(toList()) // materialize before closing the jar | ||
.stream(); | ||
} catch (final IOException ioe) { | ||
return Stream.empty(); | ||
} | ||
} | ||
|
||
// here we try to detect "well-known" indirect patterns to remove false warnings | ||
private void cleanupUnused(final Set<String> spi, final Set<Artifact> unusedDeclared) { | ||
unusedDeclared.removeIf(this::isSlf4jBinding); | ||
unusedDeclared.removeIf(it -> hasUsedSPIImpl(spi, it)); | ||
} | ||
|
||
// mainly for v1.x line but doesn't hurt much for v2 | ||
// TODO: enhance to ensure there is a single binding else just log a warning for all | ||
// and maybe even handle version? | ||
private boolean isSlf4jBinding(final Artifact artifact) { | ||
try (JarFile file = new JarFile(artifact.getFile())) { | ||
return file.getEntry("org/slf4j/impl/StaticLoggerBinder.class") != null; | ||
} catch (final IOException e) { | ||
return false; | ||
} | ||
} | ||
|
||
private boolean hasUsedSPIImpl(final Set<String> usedSpi, final Artifact artifact) { | ||
final Set<String> spi; | ||
try (JarFile file = new JarFile(artifact.getFile())) { | ||
spi = list(file.entries()).stream() | ||
.filter(it -> it.getName().startsWith("META-INF/services/") && !it.isDirectory()) | ||
.map(it -> it.getName().substring("META-INF/services/".length())) | ||
.collect(toSet()); | ||
|
||
// java >= 9 | ||
final JarEntry moduleEntry = file.getJarEntry("module-info.class"); | ||
if (moduleEntry != null) { | ||
try (InputStream in = file.getInputStream(moduleEntry)) { | ||
final ClassReader cr = new ClassReader(in); | ||
cr.accept( | ||
new ClassVisitor(ASM9) { | ||
@Override | ||
public ModuleVisitor visitModule(String name, int access, String version) { | ||
return new ModuleVisitor(ASM9) { | ||
@Override | ||
public void visitProvide(final String service, final String[] providers) { | ||
spi.add(service.replace('/', '.')); | ||
} | ||
}; | ||
} | ||
}, | ||
0); | ||
} | ||
} | ||
} catch (final IOException e) { | ||
return false; | ||
} | ||
return usedSpi.stream().anyMatch(spi::contains); | ||
} | ||
|
||
private void filterArtifactsByScope(Set<Artifact> artifacts, String scope) { | ||
artifacts.removeIf(artifact -> artifact.getScope().equals(scope)); | ||
} | ||
|
@@ -559,7 +695,8 @@ private void writeScriptableOutput(Set<Artifact> artifacts) { | |
} | ||
|
||
private List<Artifact> filterDependencies(Set<Artifact> artifacts, String[] excludes) { | ||
ArtifactFilter filter = new StrictPatternExcludesArtifactFilter(Arrays.asList(excludes)); | ||
ArtifactFilter filter = new StrictPatternExcludesArtifactFilter( | ||
excludes == null ? Collections.emptyList() : Arrays.asList(excludes)); | ||
List<Artifact> result = new ArrayList<>(); | ||
|
||
for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) { | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please don't change