Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ private static String parseUrn(String resourceName) throws IOException {

private static List<String> getExtensionYamlFiles() {
try (ScanResult scan =
new ClassGraph().acceptPathsNonRecursive("substrait/extensions").scan()) {
new ClassGraph()
.enableClasspath()
.enableNonSystemModules()
.acceptPathsNonRecursive("substrait/extensions")
.scan()) {
ResourceList resources = scan.getResourcesWithExtension(".yaml");
return resources.stream()
.map(r -> r.getPath().substring("substrait/extensions/".length()))
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
antlr = "4.13.2"
calcite = "1.42.0"
classgraph = "4.8.197"
classgraph = "5.0.0"
graal = "25.0.2"
graal-plugin = "1.1.14"
gradle-extensions = "4.0.0"
Expand Down
8 changes: 4 additions & 4 deletions isthmus-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ graalvmNative {
// src/main/resources/META-INF/native-image/.../reachability-metadata.json.
buildArgs.add("-H:+ReportExceptionStackTraces")
buildArgs.add("--features=io.substrait.isthmus.cli.RegisterAtRuntime")
// ClassGraph registers this FileSystemProvider via META-INF/services. RegisterAtRuntime
// scans with ClassGraph inside the image builder, which leaves the provider in the JDK's
// cached list of installed providers, and so in the image heap.
buildArgs.add("--initialize-at-build-time=io.github.classgraph.vfs.VfsFileSystemProvider")
// Removed due to https://github.com/oracle/graal/issues/13316
// buildArgs.add("--future-defaults=all")
jvmArgs.add("--sun-misc-unsafe-memory-access=allow")

// Capture the classpath since native-compile erases it
val isthmusClasspath = classpath.joinToString(":")
jvmArgs.add("-Disthmus.classpath=$isthmusClasspath")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.ProtocolMessageEnum;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import io.substrait.extension.SimpleExtension;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import org.apache.calcite.rel.metadata.BuiltInMetadata;
import org.apache.calcite.rel.metadata.Metadata;
import org.apache.calcite.rel.metadata.MetadataHandler;
Expand Down Expand Up @@ -60,7 +63,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
// Empty class
register(Empty.class);

try (PackageScanner substrait = new PackageScanner("io.substrait")) {
try (PackageScanner substrait = new PackageScanner(access, "io.substrait")) {
// protobuf items
substrait.registerByParent(GeneratedMessage.class);
substrait.registerByParent(GeneratedMessage.Builder.class);
Expand All @@ -83,7 +86,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
SqlStdOperatorTable.class,
StandardConvertletTable.class);

try (PackageScanner calcite = new PackageScanner("org.apache.calcite")) {
try (PackageScanner calcite = new PackageScanner(access, "org.apache.calcite")) {
calcite.registerByParent(Metadata.class);
calcite.registerByParent(MetadataHandler.class);
calcite.registerByParent(Resources.Element.class);
Expand Down Expand Up @@ -146,33 +149,49 @@ private static void register(Class<?>... classes) {
}

private static final class PackageScanner implements AutoCloseable {
private final Feature.FeatureAccess access;
private final ScanResult scan;

PackageScanner(String... packageNames) {
PackageScanner(Feature.FeatureAccess access, String... packageNames) {
this.access = access;
scan =
new ClassGraph()
.enableAllInfo()
// GraalVM native-compile erases the classloader classpath
.overrideClasspath(System.getProperty("isthmus.classpath"))
.enableClasspathEntries(access.getApplicationClassPath())
.enableClassInfo()
.enableAnnotationInfo()
.ignoreClassVisibility()
.acceptPackages(packageNames)
.scan();
}

void registerByAnnotation(Class<? extends Annotation> annotation) {
scan.getClassesWithAnnotation(annotation).loadClasses().forEach(this::registerByParent);
loadClasses(scan.getClassesWithAnnotation(annotation)).forEach(this::registerByParent);
}

void registerByParent(Class<?> c) {
register(c);
getSubTypes(c).loadClasses().forEach(RegisterAtRuntime::register);
loadClasses(getSubTypes(c)).forEach(RegisterAtRuntime::register);
}

private ClassInfoList getSubTypes(Class<?> c) {
if (c.isInterface()) {
return scan.getClassesImplementing(c);
return scan.getAllClassesImplementing(c);
}

return scan.getSubclasses(c);
return scan.getAllSubclasses(c);
}

private List<Class<?>> loadClasses(ClassInfoList classInfos) {
List<Class<?>> classes = new ArrayList<>();
for (ClassInfo classInfo : classInfos) {
Class<?> c = access.findClassByName(classInfo.getName());
if (c == null) {
throw new IllegalStateException("Class not found: " + classInfo.getName());
}
classes.add(c);
}
return classes;
}
Comment on lines +185 to 195

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findClassByName doesn't only return null for a missing class. In GraalVM 25.0.2 it is imageClassLoader.findClass(className).get(). ImageClassLoader.findClass catches both ClassNotFoundException and LinkageError and wraps them in a TypeResult whose get() returns null. So a class that is on the classpath but fails to link, for example a NoClassDefFoundError from a missing optional dependency, is reported as "Class not found" and the real cause is lost. ClassGraph 4's loadClasses() kept the cause: it threw IllegalArgumentException("Could not load class …", e).

findClassByName loads through getApplicationClassLoader(), so calling Class.forName on that loader behaves the same and keeps the exception:

Suggested change
private List<Class<?>> loadClasses(ClassInfoList classInfos) {
List<Class<?>> classes = new ArrayList<>();
for (ClassInfo classInfo : classInfos) {
Class<?> c = access.findClassByName(classInfo.getName());
if (c == null) {
throw new IllegalStateException("Class not found: " + classInfo.getName());
}
classes.add(c);
}
return classes;
}
private List<Class<?>> loadClasses(ClassInfoList classInfos) {
ClassLoader loader = access.getApplicationClassLoader();
List<Class<?>> classes = new ArrayList<>();
for (ClassInfo classInfo : classInfos) {
try {
classes.add(Class.forName(classInfo.getName(), false, loader));
} catch (ClassNotFoundException | LinkageError e) {
throw new IllegalStateException("Could not load class " + classInfo.getName(), e);
}
}
return classes;
}


@Override
Expand Down
Loading