Throw ClassNotFoundException for missing class resource in ThrowawayClassLoader

Prior to this commit, ThrowawayClassLoader.loadClass fell back to
loadClassFromResource(), which returns null when no class resource is
available. Returning null from loadClass violates the ClassLoader
contract and leads to a NullPointerException in callers such as
PreComputeFieldFeature.

To address that, this commit rethrows the original
ClassNotFoundException when the resource fallback yields no class.

Closes gh-36938

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
This commit is contained in:
junhyeong9812
2026-06-17 15:47:27 +02:00
committed by Sam Brannen
parent 077bfaf095
commit 233e7b91f9
2 changed files with 23 additions and 1 deletions
@@ -54,7 +54,11 @@ class ThrowawayClassLoader extends ClassLoader {
return super.loadClass(name, true);
}
catch (ClassNotFoundException ex) {
return loadClassFromResource(name);
Class<?> loadedFromResource = loadClassFromResource(name);
if (loadedFromResource == null) {
throw ex;
}
return loadedFromResource;
}
}
}
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link ThrowawayClassLoader}.
@@ -56,6 +57,23 @@ class ThrowawayClassLoaderTests {
assertThat(closed).as("InputStream closed").isTrue();
}
@Test
void loadClassThrowsClassNotFoundExceptionWhenClassResourceIsMissing() {
// The grandparent resolves bootstrap classes only, so super.loadClass(...) fails,
// and the resource loader provides no class bytes. The fallback must then honor the
// ClassLoader.loadClass contract by reporting the failure instead of returning null.
ClassLoader resourceLoader = new ClassLoader(new ClassLoader(null) {}) {
@Override
public InputStream getResourceAsStream(String name) {
return null;
}
};
ThrowawayClassLoader classLoader = new ThrowawayClassLoader(resourceLoader);
assertThatExceptionOfType(ClassNotFoundException.class)
.isThrownBy(() -> classLoader.loadClass("com.example.MissingClass"));
}
private static byte[] classBytesOf(String className) throws IOException {
String resourceName = className.replace('.', '/') + ".class";
try (InputStream in = ThrowawayClassLoaderTests.class.getClassLoader().getResourceAsStream(resourceName)) {