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

Various smaller adjustments #42

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2aaa211
Update to JUnit 4.12
centic9 Dec 16, 2017
44c6271
Adjust a typo and unused imports
centic9 Dec 16, 2017
565b5f3
Set compiler to Java 1.7
centic9 Mar 29, 2018
cda43d1
Ignore IntelliJ project file
centic9 Oct 30, 2018
20a11ed
[JENKINS-53394] Check null options
MRamonLeon Oct 30, 2018
15493b7
[maven-release-plugin] prepare release file-leak-detector-1.13
kohsuke Oct 30, 2018
ccb7f69
[maven-release-plugin] prepare for next development iteration
kohsuke Oct 30, 2018
88e5423
Enhance test, taken from #47
centic9 Jan 3, 2020
685e74b
Improve handling of Parameterized in unit-test and some other test im…
centic9 Jan 3, 2020
d2c59de
Fix IDE warning about generics
centic9 Jan 3, 2020
f81b5bb
Add a test for AgentMain which verifies that all classes from the spe…
centic9 Jan 3, 2020
5d4bc39
Add support for Files.newByteChannel()
centic9 Jan 3, 2020
b1ed145
Try to instrument some classes which are not available in all version…
centic9 Jan 3, 2020
a52b9e4
Add a commented test which only works with Java 8+
centic9 Jan 3, 2020
4d847e8
Add initial README to explain a bit how to develop for this project
centic9 Jan 3, 2020
8874504
Add support for Files.newDirectoryStream, closes #35
centic9 Jan 3, 2020
31c4e6a
Also verify that the correct type of object is registered as 'marker'…
centic9 Jan 3, 2020
b416d30
Update minimum Java to 1.8 to enable some more tests
centic9 Sep 17, 2020
600830c
Add failing test which uses a custom filesystem which LuceneTestCase …
centic9 Sep 17, 2020
ba46bd1
Tried to add some more functionality, but did not work
centic9 Oct 6, 2021
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/.idea/*/*
/.idea/*
/.idea
/file-leak-detector.iml
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
A Java agent which detects file handle leaks.

See http://file-leak-detector.kohsuke.org/ for usage description

# Development

## Implementation details

This project uses the JVM support for instrumenting Java classes during startup.

It adds code to various places where files or sockets are opened and closed to
print out which file handles have not been closed correctly.

## How to build

mvn package

The resulting package will be at `target/file-leak-detector-1.<version>-SNAPSHOT-jar-with-dependencies.jar`

## How to run integration tests

mvn verify

This will run tests in the `org.kohsuke.file_leak_detector.instrumented` package which are
executed with instrumentation via the Java agent being active.

32 changes: 23 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</parent>
<groupId>org.kohsuke</groupId>
<artifactId>file-leak-detector</artifactId>
<version>1.13-SNAPSHOT</version>
<version>1.14-SNAPSHOT</version>
<name>File Leak Detector</name>

<url>http://${project.artifactId}.kohsuke.org/</url>
Expand All @@ -31,8 +31,8 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
Expand All @@ -53,6 +53,7 @@
</includes>
<excludes>
<exclude>**/TransformerTest.java</exclude>
<exclude>**/AgentMainTest.java</exclude>
</excludes>
<argLine>
-javaagent:"${project.build.directory}/file-leak-detector-${project.version}-jar-with-dependencies.jar"
Expand All @@ -67,9 +68,8 @@
</goals>
<configuration>
<includes>
<include>
**/TransformerTest.java
</include>
<include>**/TransformerTest.java</include>
<include>**/AgentMainTest.java</include>
</includes>
<excludes>
<exclude>**/instrumented/*.java</exclude>
Expand Down Expand Up @@ -116,6 +116,14 @@
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>

Expand All @@ -130,13 +138,13 @@
<artifactId>args4j</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<!--dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>6.0</version>
<scope>system</scope>
<systemPath>${toolsjar}</systemPath>
</dependency>
</dependency-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
Expand All @@ -146,7 +154,13 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.4</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
66 changes: 57 additions & 9 deletions src/main/java/org/kohsuke/file_leak_detector/AgentMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
import java.net.Socket;
import java.net.SocketImpl;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
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;
import java.util.List;
Expand Down Expand Up @@ -52,10 +55,10 @@ public class AgentMain {
public static void agentmain(String agentArguments, Instrumentation instrumentation) throws Exception {
premain(agentArguments,instrumentation);
}

public static void premain(String agentArguments, Instrumentation instrumentation) throws Exception {
int serverPort = -1;

if(agentArguments!=null) {
// used by Main to prevent the termination of target JVM
boolean quit = true;
Expand Down Expand Up @@ -133,8 +136,8 @@ public void run() {

Listener.AGENT_INSTALLED = true;
instrumentation.addTransformer(new TransformerImpl(createSpec()),true);
instrumentation.retransformClasses(

List<Class> classes = Arrays.asList(new Class[] {
FileInputStream.class,
FileOutputStream.class,
RandomAccessFile.class,
Expand All @@ -143,19 +146,37 @@ public void run() {
AbstractSelectableChannel.class,
AbstractInterruptibleChannel.class,
FileChannel.class,
AbstractSelector.class
);
AbstractSelector.class,
Files.class});

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]));


// Socket.class,
// SocketChannel.class,
// AbstractInterruptibleChannel.class,
// ServerSocket.class);

/*instrumentation.addTransformer(new InterfaceTransformerImpl(createSpec()),true);
instrumentation.retransformClasses(classes.toArray(new Class[0]));*/

if (serverPort>=0)
runHttpServer(serverPort);
}

private static void addIfFound(List<Class> classes, String className) {
try {
classes.add(Class.forName(className));
} catch(ClassNotFoundException e) {
// ignored here
}
}

private static void runHttpServer(int port) throws IOException {
final ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("localhost", port));
Expand Down Expand Up @@ -227,6 +248,22 @@ static List<ClassTransformSpec> createSpec() {
new ClassTransformSpec(FileChannel.class,
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)),
/*
* Detect instances opened via static methods in class java.nio.file.Files
*/
new ClassTransformSpec(Files.class,
// SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> 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),
// DirectoryStream<Path> newDirectoryStream(Path dir)
new ReturnFromStaticMethodInterceptor("newDirectoryStream",
"(Ljava/nio/file/Path;)Ljava/nio/file/DirectoryStream;", 2, "open_directorystream", DirectoryStream.class, Path.class),
// DirectoryStream<Path> 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<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> 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
*/
Expand All @@ -237,6 +274,16 @@ static List<ClassTransformSpec> createSpec() {
*/
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")),
/*new ClassTransformSpec("java/nio/file/DirectoryStream",
new CloseInterceptor("close")),*/

/**
* Detect selectors, which may open native pipes and anonymous inodes for event polling.
Expand All @@ -249,7 +296,7 @@ static List<ClassTransformSpec> createSpec() {
java.net.Socket/ServerSocket uses SocketImpl, and this is where FileDescriptors
are actually managed.

SocketInputStream/SocketOutputStream does not maintain a separate FileDescritor.
SocketInputStream/SocketOutputStream does not maintain a separate FileDescriptor.
They just all piggy back on the same SocketImpl instance.
*/
new ClassTransformSpec("java/net/PlainSocketImpl",
Expand Down Expand Up @@ -353,6 +400,7 @@ protected void append(CodeGenerator g) {
/**
* Used to intercept {@link java.net.PlainSocketImpl#accept(SocketImpl)}
*/
@SuppressWarnings("JavadocReference")
private static class AcceptInterceptor extends MethodAppender {
public AcceptInterceptor(String name, String desc) {
super(name,desc);
Expand Down Expand Up @@ -398,7 +446,7 @@ private OpenInterceptionAdapter(MethodVisitor base, int access, String desc) {
* Decide if this is the method that needs interception.
*/
protected abstract boolean toIntercept(String owner, String name);

protected Class<? extends Exception> getExpectedException() {
return IOException.class;
}
Expand All @@ -407,7 +455,7 @@ protected Class<? extends Exception> getExpectedException() {
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if(toIntercept(owner,name)) {
Type exceptionType = Type.getType(getExpectedException());

CodeGenerator g = new CodeGenerator(mv);
Label s = new Label(); // start of the try block
Label e = new Label(); // end of the try block
Expand Down
28 changes: 19 additions & 9 deletions src/main/java/org/kohsuke/file_leak_detector/Listener.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import java.net.SocketImpl;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
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;
Expand All @@ -28,7 +30,7 @@

/**
* Intercepted JDK calls land here.
*
*
* @author Kohsuke Kawaguchi
*/
public class Listener {
Expand Down Expand Up @@ -60,7 +62,7 @@ public void dump(String prefix, PrintWriter pw) {
pw.println("\tat " + trace[i]);
pw.flush();
}

public boolean exclude() {
if(EXCLUDES.isEmpty()) {
return false;
Expand All @@ -75,7 +77,7 @@ public boolean exclude() {
break;
}
}

// check the rest
for (; i < trace.length; i++) {
String t = trace[i].toString();
Expand Down Expand Up @@ -244,7 +246,7 @@ public void dump(String prefix, PrintWriter ps) {
public static PrintWriter ERROR = new PrintWriter(System.err);

/**
* Allows to provide stacktrace-lines which cause the element to be excluded
* Allows to provide stacktrace-lines which cause the element to be excluded
*/
public static final List<String> EXCLUDES = new ArrayList<String>();

Expand All @@ -270,7 +272,7 @@ public void dump(String prefix, PrintWriter ps) {
public static boolean isAgentInstalled() {
return AGENT_INSTALLED;
}

public static synchronized void makeStrong() {
TABLE = new LinkedHashMap<Object, Record>(TABLE);
}
Expand Down Expand Up @@ -309,6 +311,14 @@ public static synchronized void open_filechannel(FileChannel fileChannel, Path p
open(fileChannel, path.toFile());
}

public static synchronized void open_filechannel(SeekableByteChannel byteChannel, Path path) {
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));
Expand Down Expand Up @@ -353,11 +363,11 @@ public static synchronized void openSocket(Object _this) {
}
}
}

public static synchronized List<Record> getCurrentOpenFiles() {
return new ArrayList<Record>(TABLE.values());
}

private static synchronized void put(Object _this, Record r) {
// handle excludes
if(r.exclude()) {
Expand Down Expand Up @@ -435,7 +445,7 @@ public static synchronized void outOfDescriptors() {
tracing = false;
}
}

private static String format(long time) {
try {
return new Date(time).toString();
Expand All @@ -445,7 +455,7 @@ private static String format(long time) {
}

private static Field SOCKETIMPL_SOCKET,SOCKETIMPL_SERVER_SOCKET;

static {
try {
SOCKETIMPL_SOCKET = SocketImpl.class.getDeclaredField("socket");
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/kohsuke/file_leak_detector/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void run() throws Exception {
System.out.println("Activating file leak detector at "+agentJar);
// load a specified agent onto the JVM
// pass the hidden option to prevent this from killing the target JVM if the options were wrong
api.getMethod("loadAgent",String.class,String.class).invoke(vm, agentJar.getPath(), "noexit,"+options);
api.getMethod("loadAgent",String.class,String.class).invoke(vm, agentJar.getPath(), options == null ? "noexit" : "noexit,"+options);
} finally {
api.getMethod("detach").invoke(vm);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public final class ClassTransformSpec {
public final String name;
/*package*/ Map<String,MethodTransformSpec> methodSpecs = new HashMap<String,MethodTransformSpec>();

public ClassTransformSpec(Class clazz, MethodTransformSpec... methodSpecs) {
public ClassTransformSpec(Class<?> clazz, MethodTransformSpec... methodSpecs) {
this(clazz.getName().replace('.', '/'),methodSpecs);
}

Expand Down
Loading