Extend a final class or replace it

Abr*_*kot 2 java final java-8

"No, it can't be done"

It has been discussed a lot of times and the only reply is "No, it can't be done". But guys we do code. Everything is theoretically possible with some time.

Problem

Basically I'm trying to extend a final class to change a single behavior. That can't be done in a traditional way - at compile time with an extend keyword. I have looked for some other ways:

  • create a proxy - that works only for interfaces;
  • edit the Modifiers of the class, remove the final modifier and extend the class at runtime - that can be done for fields but not for classes;
  • use a custom ClassLoader to load a new version of the class - it didn't work because the class I'd like to extend is part of the java package. A ClassLoader fortunately can't load a class of this package because of a SecurityException. Otherwise, that would mess up a lot of securities.
static class TestClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.equals("java.io.StringReader")) {
            try {
                InputStream is = Test.class.getClassLoader().getResourceAsStream("java/io/StringReader.class");
                byte[] buf = new byte[10000];
                int len = is.read(buf);
                return defineClass(name, buf, 0, len);  // This line throws the SecurityException :(
            } catch (IOException e) {
                throw new ClassNotFoundException("", e);
            }
        }
        return getParent().loadClass(name);
    }
}
Run Code Online (Sandbox Code Playgroud)

So here we are. Is there any method I could use to replace or extend my final class?

PS: I use Java 8 as the SecurityManager is more keen on ugly things than in newer Java versions :)


Context

I have an assignment - erk, that's never well received on SO: I have to write unit tests for a class that I can't change. This part is really easy except that a catch block can't be accessed:

Properties props = new Properties();
try
{
    props.load(new StringReader(rules));
} catch (IOException e)
{
    e.printStackTrace();
    throw e;
}
Run Code Online (Sandbox Code Playgroud)

The code coverage report our teacher sent us shows that he hasn't tested this block so we don't have to. But I don't trust Java and I would be sure this block works as expected. Then I have to test it!

[Note: as it is not required it is just a mean to discover new Java hacks and it won't probably even count in the final grade.]


The only variable I can access to is the String rules. I should then work on it to make the load call throw an IOException. The only reason for a load call to throw an IOException is when the stream of the StringReader is closed. This is checked by the ensureOpen method:

/** Check to make sure that the stream has not been closed */
private void ensureOpen() throws IOException {
    if (str == null)
        throw new IOException("Stream closed");
}
Run Code Online (Sandbox Code Playgroud)

Therefore only a null String can make this method throw an IOException. It would then be easy to inject a null String into rules. The bad news is the StringReader constructor:

public StringReader(String s) {
    this.str = s;
    this.length = s.length();
}
Run Code Online (Sandbox Code Playgroud)

It throws a NPE when passing a null String. Erk.

The last idea I had was to change the value of the String to make its inner value field null - easy when working with the Modifiers of this field. This didn't work because the reference not the value itself should be null; this would only throw a NPE because of the invocation of the read method on the StringReader:

public int read() throws IOException {
    synchronized (lock) {
        ensureOpen();
        if (next >= length)
            return -1;
        return str.charAt(next++);
    }
}
Run Code Online (Sandbox Code Playgroud)

Do you have any idea? A framework that would do this for me? Some path I could explore to solve my useless problem?

This question is not about code testing. It is about how to hack Java and its final classes.

Hol*_*ger 5

Instrumentation API,它允许Java软件(即Java代理)在运行时修改类。这是模拟框架执行工作的方式之一。将其用于单元测试并非没有道理。

但是请注意,代码注入的特定目标是错误的。在代码中

Properties props = new Properties();
try
{
    props.load(new StringReader(rules));
} catch (IOException e)
{
    e.printStackTrace();
    throw e;
}
Run Code Online (Sandbox Code Playgroud)

IOException永远不会抛出。正如您自己指出的那样,StringReader的构造函数不会抛出它,并且对它的read方法也是如此。声明它的load方法Properties,因为它必须处理任意Reader实现,但它自己不会产生此类异常。

因此,在这种情况下,当您从a读取String具有新构造的时StringReader,将永远不会抛出该异常,并且要注入一个实际上不存在的行为来测试不会发生的情况,这是没有意义的。

正如您自己指出的那样,它唯一可能抛出的地方IOException就是方法ensureOpen(),顾名思义,该方法检查读者是否仍然打开。因此,您可以通过简单地调用close()读取器来强制抛出行为,但是,正如所说的那样,它与被测代码的实际行为不匹配,并且激发被测异常也没有意义,实际上不会发生。