运行期代码问题检查技术的研究

以下用我之前代码中的一个bug作为说明,解释如何实现代码在运行期的问题检查。

首先,我们先定义一下待检查的问题。故障代码如下:
	// Invocator...
		try {
			channel = new TcpChannel(SocketChannel.open(), new InetSocketAddress("127.0.0.1", 5656));
		} catch (Exception ex) {
			if (channel != null) {// **
				channel.cleanup(); // Error prone?
			}
			throw ex;
		}

	// TcpChannel.java
	public TcpChannel(SelectableChannel channel, SocketAddress addr) throws IOException
	{
		boolean done = false;
		Selector selector = null;
		try {
			selector = Selector.open();
			key = channel.register(selector, op);
			// ...
			done = true;
		} finally {
			if (!done) {
				cleanup(); // Close all resources
			}
		}
		connect(addr); // * IOException ?
	}

	void cleanup()
	{
		try {
			key.selector().close();
			key.channel().close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	private void connect(SocketAddress addr) throws IOException
	// ...

这个问题是在使用SocketChannel的时候,没有正确地关闭,导致系统资源(文件句柄)泄漏。具体是由于connect(addr);*操作可能产生IOException错误,所以channel可能会构造失败,从而不能执行Exception子句**中的资源回收,造成的句柄外泄。但由于代码里明确写明了SocketChannel的打开和关闭,所以仅从静态的代码审查角度来看,很难发现这个错误。所以我们可以在这里采用对运行中的代码调用指定检查逻辑的方法以发现这种问题。
首先,还是要建立我们的判断逻辑。
public final class SelectorOp
{
	private SelectorOp() {}

	public static Selector open() throws IOException
	{
		Selector selector = Selector.open();
		System.out.println(selector + "\topened!");
		return selector;
	}

	public static void close(Selector selector) throws IOException
	{
		System.out.println(selector + "\tclosed!");
		selector.close();
	}
}

这里,如果要新建一个Selector,则调用SelectorOp.open()代理方法,完成对原有Selector.open()方法的调用,并记录被创建的Selector;如果要关闭Selector,也需要调用SelectorOp.close(selector)代理方法,完成对selector对象自身close()方法的调用,并记录。
这个例子只是一个问题检查的简化说明,只具有实验意义。对于真正的应用来说,可以考虑把这些事件记录在类似于Map这样的容器中,并收集事件发生的时间和代码行,以便做进一步的对比和分析。
接下来,我们要实现自己的ClassLoader。具体的代码见附件,这里仅拣要点说明。
public FileClassLoader(String[] classPaths) {
	for (String path : classPaths) {
		File file = new File(path);
		if (file.exists() && file.canRead()) {
			if (file.isFile() && file.getName().endsWith(".jar")) {
				loadJar(file);
			} else if (file.isDirectory()) {
				loadPath(file, "");
			}
		}
	}
}

FileClassLoader可以处理单个文件,也可以处理jar包。不论对于哪种方式,都是添加类名->实际存放位置的映射关系。之后,在需要的时候获取类的二进制字节数组,转换为Class。
最后,我们要实现类结构的访问和修改工具,见下:
public void visitMethodInsn(int opcode, String owner, String name, String desc)
{
	final String OWNER = "java/nio/channels/Selector";
	final String MOCK = "mock/SelectorOp";
	final String DESC1 = "()Ljava/nio/channels/Selector;";
	final String DESC2 = "(Ljava/nio/channels/Selector;)V";
	if (opcode == INVOKESTATIC && OWNER.equals(owner) && "open".equals(name) && DESC1.equals(desc)) {
		super.visitMethodInsn(opcode, MOCK, name, desc);
	} else
	if (opcode == INVOKEVIRTUAL && OWNER.equals(owner) && "close".equals(name) && "()V".equals(desc)) {
		super.visitMethodInsn(Opcodes.INVOKESTATIC, MOCK, name, DESC2);
	} else {
		super.visitMethodInsn(opcode, owner, name, desc);
	}
}

如果发现了对Selector的open静态方法调用,则转为调用SelectorOp的open静态方法;如果发现了对Selector对象的close调用,则转为调用SelectorOp的close静态方法,入参为Selector对象。
执行结果如下:
运行期代码问题检查技术的研究_第1张图片

图中可以很容易发现,Selector只有开放,没有关闭。
但修改过代码之后,再次执行则结果如下:
运行期代码问题检查技术的研究_第2张图片

每个Selector对象都正确地开放和关闭了。

你可能感兴趣的:(代码)