We tried, over a few rounds of the condy spec, to make claims about what kind
of exceptions can be thrown during resolution. Specifically, that anything
thrown will be an Error.
Bootstrap methods execute arbitrary code, so we wrap any non-Error that gets
thrown in a BootstrapMethodError. I tried finding the equivalent rule for
ClassLoaders, and could not. With some testing, it turns out that, in fact,
exceptions thrown by ClassLoaders are propagated without wrapping. See the test
below.
So, it turns out, resolution (of classes) can result in *any* kind of exception
being thrown.
This is very old behavior, and I'm pretty sure we don't want to perturb it, but
I thought it would be good to raise awareness of it. I'll make sure JVMS, as
modified for condy, treats it appropriately.
—Dan
Test code:
~~~
public class Test {
public static void main(String... args) throws Exception {
PreemptingClassLoader l = new PreemptingClassLoader("A", "B");
Class<?> c = l.loadClass("A");
Runnable r = (Runnable) c.getDeclaredConstructor().newInstance();
r.run();
}
}
~~~
public class A implements Runnable {
public void run() {
System.out.println(B.foo);
}
}
~~~
public class B {
public static String foo = "foo";
}
~~~
import java.util.*;
import java.io.*;
public class PreemptingClassLoader extends ClassLoader {
private final Set<String> names = new HashSet<>();
public PreemptingClassLoader(String... names) {
for (String n : names) this.names.add(n);
}
protected Class<?> loadClass(String name, boolean resolve) throws
ClassNotFoundException {
if (!names.contains(name)) return super.loadClass(name, resolve);
Class<?> result = findLoadedClass(name);
if (result == null) {
if (name.equals("B")) throw new RuntimeException("Hi, mom"); // INSERTED
TO TRIGGER EXCEPTION
String filename = name.replace('.', '/') + ".class";
try (InputStream data = getResourceAsStream(filename)) {
if (data == null) throw new ClassNotFoundException();
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
int b;
do {
b = data.read();
if (b >= 0) buffer.write(b);
} while (b >= 0);
byte[] bytes = buffer.toByteArray();
result = defineClass(name, bytes, 0, bytes.length);
}
}
catch (IOException e) {
throw new ClassNotFoundException("Error reading class file", e);
}
}
if (resolve) resolveClass(result);
return result;
}
}