diff --git a/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java b/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java index 97b7a35..6d13ca5 100644 --- a/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java +++ b/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java @@ -25,6 +25,7 @@ import java.nio.channels.spi.AbstractInterruptibleChannel; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.AbstractSelector; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -150,6 +151,8 @@ public void run() { addIfFound(classes, "sun/nio/ch/SocketChannelImpl"); addIfFound(classes, "java/net/AbstractPlainSocketImpl"); + addIfFound(classes, "sun/nio/fs/UnixDirectoryStream"); + addIfFound(classes, "sun/nio/fs/UnixSecureDirectoryStream"); instrumentation.retransformClasses(classes.toArray(new Class[0])); @@ -243,11 +246,21 @@ static List createSpec() { new ReturnFromStaticMethodInterceptor("open", "(Ljava/nio/file/Path;Ljava/util/Set;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/channels/FileChannel;", 4, "open_filechannel", FileChannel.class, Path.class)), /* - SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) + * Detect instances opened via static methods in class java.nio.file.Files */ new ClassTransformSpec(Files.class, + // SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) new ReturnFromStaticMethodInterceptor("newByteChannel", - "(Ljava/nio/file/Path;Ljava/util/Set;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/channels/SeekableByteChannel;", 4, "open_filechannel", SeekableByteChannel.class, Path.class)), + "(Ljava/nio/file/Path;Ljava/util/Set;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/channels/SeekableByteChannel;", 4, "open_filechannel", SeekableByteChannel.class, Path.class), + // DirectoryStream newDirectoryStream(Path dir) + new ReturnFromStaticMethodInterceptor("newDirectoryStream", + "(Ljava/nio/file/Path;)Ljava/nio/file/DirectoryStream;", 2, "open_directorystream", DirectoryStream.class, Path.class), + // DirectoryStream newDirectoryStream(Path dir, String glob) + new ReturnFromStaticMethodInterceptor("newDirectoryStream", + "(Ljava/nio/file/Path;Ljava/lang/String;)Ljava/nio/file/DirectoryStream;", 6, "open_directorystream", DirectoryStream.class, Path.class), + // DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) + new ReturnFromStaticMethodInterceptor("newDirectoryStream", + "(Ljava/nio/file/Path;Ljava/nio/file/DirectoryStream$Filter;)Ljava/nio/file/DirectoryStream;", 3, "open_directorystream", DirectoryStream.class, Path.class)), /* * Detect new Pipes */ @@ -258,6 +271,14 @@ SeekableByteChannel newByteChannel(Path path, Set options, */ new ClassTransformSpec(AbstractInterruptibleChannel.class, new CloseInterceptor("close")), + /* + * We need to see closing of DirectoryStream instances, + * however they are OS-specific, so we need to list them via String-name + */ + new ClassTransformSpec("sun/nio/fs/UnixDirectoryStream", + new CloseInterceptor("close")), + new ClassTransformSpec("sun/nio/fs/UnixSecureDirectoryStream", + new CloseInterceptor("close")), /** * Detect selectors, which may open native pipes and anonymous inodes for event polling. diff --git a/src/main/java/org/kohsuke/file_leak_detector/Listener.java b/src/main/java/org/kohsuke/file_leak_detector/Listener.java index 715d0fa..d230ab2 100644 --- a/src/main/java/org/kohsuke/file_leak_detector/Listener.java +++ b/src/main/java/org/kohsuke/file_leak_detector/Listener.java @@ -18,6 +18,7 @@ import java.nio.channels.SeekableByteChannel; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; +import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.Date; @@ -314,6 +315,10 @@ public static synchronized void open_filechannel(SeekableByteChannel byteChannel open(byteChannel, path.toFile()); } + public static synchronized void open_directorystream(DirectoryStream directoryStream, Path path) { + open(directoryStream, path.toFile()); + } + public static synchronized void openSelector(Object _this) { if (_this instanceof Selector) { put(_this, new SelectorRecord((Selector)_this)); diff --git a/src/test/java/org/kohsuke/file_leak_detector/AgentMainTest.java b/src/test/java/org/kohsuke/file_leak_detector/AgentMainTest.java index ee6b979..4ee17be 100644 --- a/src/test/java/org/kohsuke/file_leak_detector/AgentMainTest.java +++ b/src/test/java/org/kohsuke/file_leak_detector/AgentMainTest.java @@ -61,6 +61,8 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable { // the following two are not available in all JVMs seenClasses.remove("sun/nio/ch/SocketChannelImpl"); seenClasses.remove("java/net/AbstractPlainSocketImpl"); + seenClasses.remove("sun/nio/fs/UnixDirectoryStream"); + seenClasses.remove("sun/nio/fs/UnixSecureDirectoryStream"); assertTrue("Had classes in the spec which were not instrumented: " + seenClasses, seenClasses.isEmpty()); diff --git a/src/test/java/org/kohsuke/file_leak_detector/instrumented/FileDemo.java b/src/test/java/org/kohsuke/file_leak_detector/instrumented/FileDemo.java index 3212257..f8e7112 100644 --- a/src/test/java/org/kohsuke/file_leak_detector/instrumented/FileDemo.java +++ b/src/test/java/org/kohsuke/file_leak_detector/instrumented/FileDemo.java @@ -10,7 +10,9 @@ import java.io.StringWriter; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; +import java.nio.file.DirectoryStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardOpenOption; import org.junit.After; @@ -88,6 +90,75 @@ public void openCloseFilesNewByteChannel() throws Exception { assertTrue(traceOutput.contains("Closed " + tempFile)); } + @Test + public void openCloseFilesNewDirectoryStream() throws Exception { + assertTrue(tempFile.delete()); + assertTrue(tempFile.mkdirs()); + + DirectoryStream stream = Files.newDirectoryStream(tempFile.toPath()); + assertNotNull("No file record for file=" + tempFile + " found", findFileRecord(tempFile)); + + stream.close(); + assertNull("File record for file=" + tempFile + " not removed", findFileRecord(tempFile)); + + String traceOutput = output.toString(); + assertTrue(traceOutput.contains("Opened " + tempFile)); + assertTrue(traceOutput.contains("Closed " + tempFile)); + } + + @Test + public void openCloseFilesNewDirectoryStreamWithStarGlob() throws Exception { + assertTrue(tempFile.delete()); + assertTrue(tempFile.mkdirs()); + + DirectoryStream stream = Files.newDirectoryStream(tempFile.toPath(), "*"); + assertNotNull("No file record for file=" + tempFile + " found", findFileRecord(tempFile)); + + stream.close(); + assertNull("File record for file=" + tempFile + " not removed", findFileRecord(tempFile)); + + String traceOutput = output.toString(); + assertTrue(traceOutput.contains("Opened " + tempFile)); + assertTrue(traceOutput.contains("Closed " + tempFile)); + } + + @Test + public void openCloseFilesNewDirectoryStreamWithNonStarGlob() throws Exception { + assertTrue(tempFile.delete()); + assertTrue(tempFile.mkdirs()); + + DirectoryStream stream = Files.newDirectoryStream(tempFile.toPath(), "my*test*glob"); + assertNotNull("No file record for file=" + tempFile + " found", findFileRecord(tempFile)); + + stream.close(); + assertNull("File record for file=" + tempFile + " not removed", findFileRecord(tempFile)); + + String traceOutput = output.toString(); + assertTrue(traceOutput.contains("Opened " + tempFile)); + assertTrue(traceOutput.contains("Closed " + tempFile)); + } + + @Test + public void openCloseFilesNewDirectoryStreamWithFilter() throws Exception { + assertTrue(tempFile.delete()); + assertTrue(tempFile.mkdirs()); + + DirectoryStream stream = Files.newDirectoryStream(tempFile.toPath(), new DirectoryStream.Filter() { + @Override + public boolean accept(Path entry) { + return true; + } + }); + assertNotNull("No file record for file=" + tempFile + " found", findFileRecord(tempFile)); + + stream.close(); + assertNull("File record for file=" + tempFile + " not removed", findFileRecord(tempFile)); + + String traceOutput = output.toString(); + assertTrue(traceOutput.contains("Opened " + tempFile)); + assertTrue(traceOutput.contains("Closed " + tempFile)); + } + /* This is only available in Java 8 and newer... @Test public void openCloseFileLines() throws Exception {