Skip to content
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

Avoid UnsupportedOperationException in ZipPath#toFile with JDK 17 #169

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down Expand Up @@ -113,7 +119,7 @@
<configuration>
<reuseForks>false</reuseForks>
<includes>
<include>**/instrumented/*.java</include>
<include>**/instrumented/**/*.java</include>
</includes>
<excludes>
<exclude>**/TransformerTest.java</exclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.zip.ZipFile;
Expand All @@ -32,6 +33,8 @@ public abstract class ActivityListener {
*/
public void open(Object obj, File file) {}

public void open(Object obj, Path file) {}

/**
* Called when a new socket is opened.
*
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/kohsuke/file_leak_detector/AgentMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ private static void addIfFound(List<Class<?>> classes, String className) {
private static void runHttpServer(int port) throws IOException {
@SuppressWarnings("resource")
final ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("localhost", port));
try {
ss.bind(new InetSocketAddress("localhost", port));
} catch (IOException e) {
throw new IOException("While binding to localhost:" + port, e);
}
System.err.println("Serving file leak stats on http://localhost:" + ss.getLocalPort() + "/ for stats");
final ExecutorService es = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r);
Expand Down
55 changes: 49 additions & 6 deletions src/main/java/org/kohsuke/file_leak_detector/Listener.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
*
* @author Kohsuke Kawaguchi
*/
@SuppressWarnings("unused")
public class Listener {

/**
* Remembers who/where/when opened a file.
*/
Expand Down Expand Up @@ -119,6 +121,25 @@ public String toString() {
}
}

public static final class PathRecord extends Record {
public final Path path;

private PathRecord(Path path) {
this.path = path;
}

@Override
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + path + " by thread:" + threadName + " on " + format(time));
super.dump(prefix, pw);
}

@Override
public String toString() {
return "PathRecord[file=" + path + "]";
}
}

public static final class SourceChannelRecord extends Record {
public final Pipe.SourceChannel source;

Expand Down Expand Up @@ -244,7 +265,7 @@ public void dump(String prefix, PrintWriter ps) {
}

/**
* Files that are currently open, keyed by the owner object (like {@link FileInputStream}.
* Files that are currently open, keyed by the owner object like {@link FileInputStream}.
*/
private static Map<Object, Record> TABLE = new WeakHashMap<>();

Expand Down Expand Up @@ -299,13 +320,35 @@ public static synchronized void makeStrong() {
* File being opened.
*/
public static synchronized void open(Object _this, File f) {
put(_this, new FileRecord(f));
put(_this, new PathRecord(f.toPath()));

for (ActivityListener al : ActivityListener.LIST) {
al.open(_this, f);
}
}

/**
* Called when a new path is opened.
*
* @param _this
* {@link FileInputStream}, {@link FileOutputStream}, {@link RandomAccessFile}, or {@link ZipFile}.
* @param p
* Path being opened.
*/
public static synchronized void open(Object _this, Path p) {
put(_this, new PathRecord(p));

for (ActivityListener al : ActivityListener.LIST) {
al.open(_this, p);
}
}

/**
* Called when a pipe is opened, e.g. via SelectorProvider
*
* @param _this
* {@link java.nio.channels.spi.SelectorProvider}
*/
public static synchronized void openPipe(Object _this) {
if (_this instanceof Pipe.SourceChannel) {
put(_this, new SourceChannelRecord((Pipe.SourceChannel) _this));
Expand All @@ -322,15 +365,15 @@ public static synchronized void openPipe(Object _this) {
}

public static synchronized void openFileChannel(FileChannel fileChannel, Path path) {
open(fileChannel, path.toFile());
open(fileChannel, path);
}

public static synchronized void openFileChannel(SeekableByteChannel byteChannel, Path path) {
open(byteChannel, path.toFile());
open(byteChannel, path);
}

public static synchronized void openDirectoryStream(DirectoryStream<?> directoryStream, Path path) {
open(directoryStream, path.toFile());
open(directoryStream, path);
}

public static synchronized void openSelector(Object _this) {
Expand Down Expand Up @@ -481,7 +524,7 @@ private static Field getSocketField(String socket) {
return socketimplSocket;
} catch (NoSuchFieldException e) {
// Java 17+ changed the implementation of Sockets and
// so the current approach does not work there any more
// so the current approach does not work there anymore
// for now we gracefully handle this and do keep file-leak-detector
// useful for other types of file-handle-leaks
System.err.println("Could not load field " + socket + " from SocketImpl: " + e);
Expand Down
57 changes: 54 additions & 3 deletions src/test/java/org/kohsuke/file_leak_detector/AgentMainTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.kohsuke.file_leak_detector;

import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
Expand All @@ -8,7 +9,11 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -29,9 +34,51 @@ public void noDuplicateSpecs() {

@Test
public void testPreMain() throws Exception {
final List<ClassTransformSpec> specs = AgentMain.createSpec();
final Set<String> seenClasses = new HashSet<>();
final Instrumentation instrumentation = prepare(seenClasses);

AgentMain.premain(null, instrumentation);

verifyInstrumentation(instrumentation, seenClasses);
}

@Test
public void testPreMainHttpServer() throws Exception {
final Set<String> seenClasses = new HashSet<>();
final Instrumentation instrumentation = prepare(seenClasses);

AgentMain.premain("http=0", instrumentation);

verifyInstrumentation(instrumentation, seenClasses);
}

@Test
public void testPreMainHttpServerInvalidPort() throws Exception {
final Set<String> seenClasses = new HashSet<>();
final Instrumentation instrumentation = prepare(seenClasses);

assertThrows(IllegalArgumentException.class, () -> AgentMain.premain("http=999999999", instrumentation));

verifyInstrumentation(instrumentation, seenClasses);
}

@Test
public void testPreMainHttpServerIOException() throws Exception {
try (final ServerSocket ss = new ServerSocket()) {
ss.bind(new InetSocketAddress("localhost", 0));

final Set<String> seenClasses = new HashSet<>();
final Instrumentation instrumentation = prepare(seenClasses);

assertThrows(IOException.class, () -> AgentMain.premain("http=" + ss.getLocalPort(), instrumentation));

verifyInstrumentation(instrumentation, seenClasses);
}
}

private static Instrumentation prepare(Set<String> seenClasses) throws UnmodifiableClassException {
final List<ClassTransformSpec> specs = AgentMain.createSpec();

for (ClassTransformSpec spec : specs) {
seenClasses.add(spec.name);
}
Expand All @@ -54,9 +101,11 @@ public void testPreMain() throws Exception {
})
.when(instrumentation)
.retransformClasses(any(Class[].class));
return instrumentation;
}

AgentMain.premain(null, instrumentation);

private static void verifyInstrumentation(Instrumentation instrumentation, Set<String> seenClasses)
throws UnmodifiableClassException {
verify(instrumentation, times(1)).addTransformer(any(), anyBoolean());
verify(instrumentation, times(1)).retransformClasses(any(Class[].class));

Expand All @@ -68,6 +117,8 @@ public void testPreMain() throws Exception {
seenClasses.remove("java/net/AbstractPlainSocketImpl");
seenClasses.remove("java/net/PlainSocketImpl");
}
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());
}
Expand Down
Loading