Skip to content

Commit

Permalink
Provide ability to track unused dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
guw committed Jun 9, 2024
1 parent 7a95d70 commit 2a087de
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public static ArrayList<Classpath> normalize(ArrayList<Classpath> classpaths) {
protected boolean annotationsFromClasspath; // should annotation files be read from the classpath (vs. explicit separate path)?
private static HashMap<File, Classpath> JRT_CLASSPATH_CACHE = null;
protected Map<String,Classpath> moduleLocations = new HashMap<>();
private Consumer<NameEnvironmentAnswer> nameEnvironmentAnswerListener; // a listener for findType* answers

/** Tasks resulting from --add-reads or --add-exports command line options. */
Map<String,UpdatesByKind> moduleUpdates = new HashMap<>();
Expand Down Expand Up @@ -447,7 +448,7 @@ private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeNam
}
answer.setBinaryType(ExternalAnnotationDecorator.create(answer.getBinaryType(), classpathEntry.getPath(),
qualifiedTypeName, zip));
return answer;
return notify(answer);
} catch (IOException e) {
// ignore broken entry, keep searching
} finally {
Expand All @@ -461,6 +462,12 @@ private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeNam
// globally configured (annotationsFromClasspath), but no .eea found, decorate in order to answer NO_EEA_FILE:
answer.setBinaryType(new ExternalAnnotationDecorator(answer.getBinaryType(), null));
}
return notify(answer);
}
private NameEnvironmentAnswer notify(NameEnvironmentAnswer answer) {
if(answer == null) return answer;
Consumer<NameEnvironmentAnswer> listener = this.nameEnvironmentAnswerListener;
if(listener != null) listener.accept(answer);
return answer;
}
private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) {
Expand Down Expand Up @@ -750,4 +757,16 @@ public void applyModuleUpdates(IUpdatableModule compilerModule, IUpdatableModule
}
}
}

/**
* @param nameEnvironmentAnswerListener
* a listener for {@link NameEnvironmentAnswer} returned by <code>findType*</code> methods; useful for
* tracking used/answered dependencies during compilation (may be <code>null</code> to unset)
* @return a previously set listener (may be <code>null</code>)
*/
public Consumer<NameEnvironmentAnswer> setNameEnvironmentAnswerListener(Consumer<NameEnvironmentAnswer> nameEnvironmentAnswerListener) {
Consumer<NameEnvironmentAnswer> existing = this.nameEnvironmentAnswerListener;
this.nameEnvironmentAnswerListener = nameEnvironmentAnswerListener;
return existing;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3842,7 +3842,7 @@ protected void handleWarningToken(String token, boolean isEnabling) {
protected void handleErrorToken(String token, boolean isEnabling) {
handleErrorOrWarningToken(token, isEnabling, ProblemSeverities.Error);
}
private void setSeverity(String compilerOptions, int severity, boolean isEnabling) {
protected void setSeverity(String compilerOptions, int severity, boolean isEnabling) {
if (isEnabling) {
switch(severity) {
case ProblemSeverities.Error :
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*******************************************************************************
* Copyright (c) 2000, 2024 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - Contributions for
* bug 295551 - Add option to automatically promote all warnings to error
* bug 185682 - Increment/decrement operators mark local variables as read
* bug 366003 - CCE in ASTNode.resolveAnnotations(ASTNode.java:639)
* bug 384663 - Package Based Annotation Compilation Error in JDT 3.8/4.2 (works in 3.7.2)
* bug 386356 - Type mismatch error with annotations and generics
* bug 331649 - [compiler][null] consider null annotations for fields
* bug 376590 - Private fields with @Inject are ignored by unused field validation
* Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
* Bug 469584 - ClassCastException in Annotation.detectStandardAnnotation (320)
* Jesper S Moller - Contributions for
* bug 384567 - [1.5][compiler] Compiler accepts illegal modifiers on package declaration
* bug 412153 - [1.8][compiler] Check validity of annotations which may be repeatable
* Ulrich Grave <[email protected]> - Contributions for
* bug 386692 - Missing "unused" warning on "autowired" fields
* Pierre-Yves B. <[email protected]> - Contributions for
* bug 542520 - [JUnit 5] Warning The method xxx from the type X is never used locally is shown when using MethodSource
* bug 546084 - Using Junit 5s MethodSource leads to ClassCastException
*******************************************************************************/
package org.eclipse.jdt.core.tests.compiler.regression;

import static java.util.stream.Collectors.joining;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.internal.compiler.batch.FileSystem;
import org.eclipse.jdt.internal.compiler.batch.Main;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.junit.Assert;

@SuppressWarnings({ "rawtypes" })
public class NameEnvironmentAnswerListenerTest extends AbstractComparableTest {

public NameEnvironmentAnswerListenerTest(String name) {
super(name);
}

// public static Test suite() {
// return buildMinimalComplianceTestSuite(testClass(), F_11);
// }

public static Class testClass() {
return NameEnvironmentAnswerListenerTest.class;
}

static class EclipseBatchCompiler extends Main {

Set<String> answeredFileNames = new LinkedHashSet<>();

public EclipseBatchCompiler(PrintWriter errAndOutWriter) {
super(errAndOutWriter, errAndOutWriter, false /* systemExitWhenFinished */, null /* customDefaultOptions */,
null /* compilationProgress */);

setSeverity(CompilerOptions.OPTION_ReportForbiddenReference, ProblemSeverities.Error, true);
setSeverity(CompilerOptions.OPTION_ReportDiscouragedReference, ProblemSeverities.Error, true);
}

@Override
public FileSystem getLibraryAccess() {
// we use this to collect information about all used dependencies during
// compilation
FileSystem nameEnvironment = super.getLibraryAccess();
nameEnvironment.setNameEnvironmentAnswerListener(this::recordNameEnvironmentAnswer);
return nameEnvironment;
}

protected void recordNameEnvironmentAnswer(NameEnvironmentAnswer answer) {
Assert.assertNotNull("don't call without answer", answer);

char[] fileName = null;
if(answer.getBinaryType() != null) {
URI uri = answer.getBinaryType().getURI();
this.answeredFileNames.add(uri.toString());
return;
} else if(answer.getCompilationUnit() != null) {
fileName = answer.getCompilationUnit().getFileName();
} else if(answer.getSourceTypes() != null && answer.getSourceTypes().length > 0) {
fileName = answer.getSourceTypes()[0].getFileName(); // the first type is guaranteed to be the requested type
} else if(answer.getResolvedBinding() != null) {
fileName = answer.getResolvedBinding().getFileName();
}
if (fileName != null) this.answeredFileNames.add(new String(fileName));
}
}

public void testNameEnvironmentAnswerListener() throws IOException {
String path = LIB_DIR;
if(!path.endsWith(File.separator)) {
path += File.separator;
}
String libPath = path + "lib.jar";
Util.createJar(
new String[] {
"p/Color.java",
"package p;\n" +
"public enum Color {\n" +
" R, Y;\n" +
" public static Color getColor() {\n" +
" return R;\n" +
" }\n" +
"}",
},
libPath, JavaCore.VERSION_17);

String unusedLibPath = path + "lib_unused.jar";
Util.createJar(
new String[] {
"p2/Color.java",
"package p2;\n" +
"public enum Color {\n" +
" R, Y;\n" +
" public static Color getColor() {\n" +
" return R;\n" +
" }\n" +
"}",
},
unusedLibPath, JavaCore.VERSION_17);

String srcDir = path + "src";
String[] pathsAndContents =
new String[] {
"s/X.java",
"package s;\n" +
"import p.Color;\n" +
"public class X {\n" +
" public static final Color MY = Color.R;\n" +
"}"
};
Util.createSourceDir(pathsAndContents, srcDir);

List<String> classpath = new ArrayList<>(Arrays.asList(getDefaultClassPaths()));
classpath.add(libPath);
classpath.add(unusedLibPath);

File outputDirectory = new File(Util.getOutputDirectory());
if (!outputDirectory.isDirectory()) {
outputDirectory.mkdirs();
}

List<String> ecjArguments = new ArrayList<>();

ecjArguments.add("-classpath");
ecjArguments.add(classpath.stream()
.map(jar -> jar.equals(unusedLibPath) ? String.format("%s[-**/*]", jar) : jar)
.collect(joining(File.pathSeparator)));


ecjArguments.add("-d");
ecjArguments.add(outputDirectory.getAbsolutePath());

ecjArguments.add("--release");
ecjArguments.add("17");

ecjArguments.add(srcDir+ File.separator + "s"+ File.separator + "X.java");

EclipseBatchCompiler compiler;
boolean compileOK;
File logFile = new File(outputDirectory, "compile.log");
try(PrintWriter log = new PrintWriter(new FileOutputStream(logFile))) {
compiler = new EclipseBatchCompiler(log);
} catch (FileNotFoundException e) {
System.out.println(getClass().getName() + '#' + getName());
e.printStackTrace();
throw new RuntimeException(e);
}
try {
compileOK = compiler.compile(ecjArguments.toArray(new String[ecjArguments.size()]));
} catch (RuntimeException e) {
compileOK = false;
System.out.println(getClass().getName() + '#' + getName());
e.printStackTrace();
throw e;
}
String logOutputString = Util.fileContent(logFile.getAbsolutePath());

if(!compileOK) {
System.out.println(logOutputString);
Assert.fail("Compile failed!");
}

Assert.assertTrue("must reference p.Color", compiler.answeredFileNames.stream().anyMatch(s -> s.contains(libPath)));
Assert.assertFalse("must not reference p2.Color", compiler.answeredFileNames.stream().anyMatch(s -> s.contains(unusedLibPath)));
}
}

0 comments on commit 2a087de

Please sign in to comment.