From 1a2fec2d277570ec3d79cd9166321e5bd76d9dc4 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 29 Nov 2018 20:12:07 +0100 Subject: [PATCH] [core] Analysis cache fails with wildcard classpath entries Fixes #1477 --- docs/pages/release_notes.md | 1 + .../pmd/cache/AbstractAnalysisCache.java | 43 ++++++++++++----- .../pmd/cache/FileAnalysisCacheTest.java | 47 ++++++++++++++++++- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 68b43f070a..2de3d3b188 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -38,6 +38,7 @@ This means, you can use CPD to find duplicated code in your Kotlin projects. * [#1341](https://github.com/pmd/pmd/issues/1341): \[doc] Documentation Error with Regex Properties * [#1468](https://github.com/pmd/pmd/issues/1468): \[doc] Missing escaping leads to XSS * [#1471](https://github.com/pmd/pmd/issues/1471): \[core] XMLRenderer: ProcessingErrors from exceptions without a message missing + * [#1477](https://github.com/pmd/pmd/issues/1477): \[core] Analysis cache fails with wildcard classpath entries * java * [#1460](https://github.com/pmd/pmd/issues/1460): \[java] Intermittent PMD failure : PMD processing errors while no violations reported * java-bestpractices diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java index 9a0fa6e508..e3eb75608d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java @@ -26,6 +26,7 @@ import java.util.logging.Logger; import java.util.zip.Adler32; import java.util.zip.CheckedInputStream; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import net.sourceforge.pmd.PMDVersion; @@ -153,28 +154,48 @@ public abstract class AbstractAnalysisCache implements AnalysisCache { ruleMapper.initialize(ruleSets); } + private static boolean isClassPathWildcard(String entry) { + return entry.endsWith("/*") || entry.endsWith("\\*"); + } + private URL[] getClassPathEntries() { final String classpath = System.getProperty("java.class.path"); final String[] classpathEntries = classpath.split(File.pathSeparator); final List entries = new ArrayList<>(); + final SimpleFileVisitor fileVisitor = new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(final Path file, + final BasicFileAttributes attrs) throws IOException { + if (!attrs.isSymbolicLink()) { // Broken link that can't be followed + entries.add(file.toUri().toURL()); + } + return FileVisitResult.CONTINUE; + } + }; + final SimpleFileVisitor jarFileVisitor = new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(final Path file, + final BasicFileAttributes attrs) throws IOException { + String extension = FilenameUtils.getExtension(file.toString()); + if ("jar".equalsIgnoreCase(extension)) { + fileVisitor.visitFile(file, attrs); + } + return FileVisitResult.CONTINUE; + } + }; + try { for (final String entry : classpathEntries) { final File f = new File(entry); - if (f.isFile()) { + if (isClassPathWildcard(entry)) { + Files.walkFileTree(new File(entry.substring(0, entry.length() - 1)).toPath(), + EnumSet.of(FileVisitOption.FOLLOW_LINKS), 1, jarFileVisitor); + } else if (f.isFile()) { entries.add(f.toURI().toURL()); } else { Files.walkFileTree(f.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, - new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(final Path file, - final BasicFileAttributes attrs) throws IOException { - if (!attrs.isSymbolicLink()) { // Broken link that can't be followed - entries.add(file.toUri().toURL()); - } - return FileVisitResult.CONTINUE; - } - }); + fileVisitor); } } } catch (final IOException e) { diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java index 50bc478d29..41837fba91 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; @@ -254,7 +255,51 @@ public class FileAnalysisCacheTest { assertFalse("Cache believes cache is up to date when a classpath file changed", reloadedCache.isUpToDate(sourceFile)); } - + + @Test + public void testWildcardClasspath() throws MalformedURLException, IOException { + final RuleSets rs = mock(RuleSets.class); + final ClassLoader cl = mock(ClassLoader.class); + setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); + + // Prepare two jar files + final File classpathJar1 = tempFolder.newFile("mylib1.jar"); + Files.write(classpathJar1.toPath(), "content of mylib1.jar".getBytes(StandardCharsets.UTF_8)); + final File classpathJar2 = tempFolder.newFile("mylib2.jar"); + Files.write(classpathJar2.toPath(), "content of mylib2.jar".getBytes(StandardCharsets.UTF_8)); + + System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + tempFolder.getRoot().getAbsolutePath() + "/*"); + + final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); + reloadedCache.checkValidity(rs, cl); + assertFalse("Cache believes cache is up to date when the classpath changed", + reloadedCache.isUpToDate(sourceFile)); + } + + @Test + public void testWildcardClasspathContentsChangeInvalidatesCache() throws MalformedURLException, IOException { + final RuleSets rs = mock(RuleSets.class); + final ClassLoader cl = mock(ClassLoader.class); + + // Prepare two jar files + final File classpathJar1 = tempFolder.newFile("mylib1.jar"); + Files.write(classpathJar1.toPath(), "content of mylib1.jar".getBytes(StandardCharsets.UTF_8)); + final File classpathJar2 = tempFolder.newFile("mylib2.jar"); + Files.write(classpathJar2.toPath(), "content of mylib2.jar".getBytes(StandardCharsets.UTF_8)); + + System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + tempFolder.getRoot().getAbsolutePath() + "/*"); + + setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); + + // Change one file's contents + Files.write(Paths.get(classpathJar2.getAbsolutePath()), "some other text".getBytes(StandardCharsets.UTF_8)); + + final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); + reloadedCache.checkValidity(rs, cl); + assertFalse("Cache believes cache is up to date when the classpath changed", + reloadedCache.isUpToDate(sourceFile)); + } + @Test public void testUnknownFileIsNotUpToDate() throws IOException { final FileAnalysisCache cache = new FileAnalysisCache(newCacheFile);