GP-4608: Improving how we handle loading classes with the same name

This commit is contained in:
Ryan Kurtz 2024-05-17 13:32:42 -04:00
parent 72d9d0eeb2
commit 87131b4762
6 changed files with 41 additions and 25 deletions

View file

@ -16,7 +16,7 @@
package ghidra.util.classfinder; package ghidra.util.classfinder;
import java.io.File; import java.io.File;
import java.util.Set; import java.util.List;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -33,8 +33,8 @@ class ClassDir {
classPackage = new ClassPackage(dir, "", monitor); classPackage = new ClassPackage(dir, "", monitor);
} }
void getClasses(Set<ClassFileInfo> set, TaskMonitor monitor) throws CancelledException { void getClasses(List<ClassFileInfo> list, TaskMonitor monitor) throws CancelledException {
classPackage.getClasses(set, monitor); classPackage.getClasses(list, monitor);
} }
String getDirPath() { String getDirPath() {

View file

@ -62,8 +62,8 @@ class ClassJar implements ClassLocation {
} }
@Override @Override
public void getClasses(Set<ClassFileInfo> set, TaskMonitor monitor) { public void getClasses(List<ClassFileInfo> list, TaskMonitor monitor) {
set.addAll(classes); list.addAll(classes);
} }
private void scanJar(TaskMonitor monitor) throws CancelledException { private void scanJar(TaskMonitor monitor) throws CancelledException {

View file

@ -15,7 +15,7 @@
*/ */
package ghidra.util.classfinder; package ghidra.util.classfinder;
import java.util.Set; import java.util.List;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -27,5 +27,5 @@ interface ClassLocation {
public static final String CLASS_EXT = ".class"; public static final String CLASS_EXT = ".class";
public void getClasses(Set<ClassFileInfo> set, TaskMonitor monitor) throws CancelledException; public void getClasses(List<ClassFileInfo> list, TaskMonitor monitor) throws CancelledException;
} }

View file

@ -89,15 +89,16 @@ class ClassPackage implements ClassLocation {
} }
@Override @Override
public void getClasses(Set<ClassFileInfo> set, TaskMonitor monitor) throws CancelledException { public void getClasses(List<ClassFileInfo> list, TaskMonitor monitor)
throws CancelledException {
set.addAll(classes); list.addAll(classes);
Iterator<ClassPackage> it = children.iterator(); Iterator<ClassPackage> it = children.iterator();
while (it.hasNext()) { while (it.hasNext()) {
monitor.checkCancelled(); monitor.checkCancelled();
ClassPackage subPkg = it.next(); ClassPackage subPkg = it.next();
subPkg.getClasses(set, monitor); subPkg.getClasses(list, monitor);
} }
} }

View file

@ -30,8 +30,6 @@ import java.util.stream.Collectors;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -81,7 +79,7 @@ public class ClassSearcher {
private static List<Class<?>> FILTER_CLASSES = Arrays.asList(ExtensionPoint.class); private static List<Class<?>> FILTER_CLASSES = Arrays.asList(ExtensionPoint.class);
private static Pattern extensionPointSuffixPattern; private static Pattern extensionPointSuffixPattern;
private static Map<String, Set<ClassFileInfo>> extensionPointSuffixToInfoMap; private static Map<String, Set<ClassFileInfo>> extensionPointSuffixToInfoMap;
private static BidiMap<ClassFileInfo, Class<?>> loadedCache = new DualHashBidiMap<>(); private static Map<ClassFileInfo, Class<?>> loadedCache = new HashMap<>();
private static Set<ClassFileInfo> falsePositiveCache = new HashSet<>(); private static Set<ClassFileInfo> falsePositiveCache = new HashSet<>();
private static volatile boolean hasSearched; private static volatile boolean hasSearched;
private static volatile boolean isSearching; private static volatile boolean isSearching;
@ -187,12 +185,6 @@ public class ClassSearcher {
Class<?> c = loadedCache.get(info); Class<?> c = loadedCache.get(info);
if (c == null) { if (c == null) {
c = loadExtensionPoint(info.path(), info.name()); c = loadExtensionPoint(info.path(), info.name());
ClassFileInfo existing = loadedCache.getKey(c);
if (existing != null) {
log.info(
"Skipping load of class '%s' from '%s'. Already loaded from '%s'."
.formatted(info.name(), info.path(), existing.path()));
}
if (c == null) { if (c == null) {
falsePositiveCache.add(info); falsePositiveCache.add(info);
continue; continue;
@ -418,8 +410,8 @@ public class ClassSearcher {
throws CancelledException { throws CancelledException {
log.info("Searching for classes..."); log.info("Searching for classes...");
Set<ClassDir> classDirs = new HashSet<>(); List<ClassDir> classDirs = new ArrayList<>();
Set<ClassJar> classJars = new HashSet<>(); List<ClassJar> classJars = new ArrayList<>();
for (String searchPath : gatherSearchPaths()) { for (String searchPath : gatherSearchPaths()) {
String lcSearchPath = searchPath.toLowerCase(); String lcSearchPath = searchPath.toLowerCase();
@ -441,17 +433,31 @@ public class ClassSearcher {
} }
} }
Set<ClassFileInfo> classSet = new HashSet<>(); List<ClassFileInfo> classList = new ArrayList<>();
for (ClassDir dir : classDirs) { for (ClassDir dir : classDirs) {
monitor.checkCancelled(); monitor.checkCancelled();
dir.getClasses(classSet, monitor); dir.getClasses(classList, monitor);
} }
for (ClassJar jar : classJars) { for (ClassJar jar : classJars) {
monitor.checkCancelled(); monitor.checkCancelled();
jar.getClasses(classSet, monitor); jar.getClasses(classList, monitor);
} }
return classSet.stream() // We can't load more than one class with the same name, so de-duplicate them
Map<String, ClassFileInfo> uniqueClassMap = new HashMap<>();
for (ClassFileInfo info : classList) {
ClassFileInfo existing = uniqueClassMap.get(info.name());
if (existing == null) {
uniqueClassMap.put(info.name(), info);
}
else {
log.info("Ignoring class '%s' from '%s'. Already found at '%s'."
.formatted(info.name(), info.path(), existing.path()));
}
}
return uniqueClassMap.values()
.stream()
.collect(Collectors.groupingBy(ClassFileInfo::suffix, Collectors.toSet())); .collect(Collectors.groupingBy(ClassFileInfo::suffix, Collectors.toSet()));
} }

View file

@ -165,6 +165,15 @@ public class GhidraLauncher {
else { /* Eclipse dev mode */ else { /* Eclipse dev mode */
// Support loading pre-built, jar-based, non-repo extensions in Eclipse dev mode // Support loading pre-built, jar-based, non-repo extensions in Eclipse dev mode
addExtensionJarPaths(classpathList, modules, layout); addExtensionJarPaths(classpathList, modules, layout);
// Eclipse launches the Utility module, so it's already on the classpath. We don't
// want to add it a second time, so remove the one we discovered.
GModule utilityModule = modules.get("Utility");
if (utilityModule == null) {
throw new IOException("Failed to find the 'Utility' module!");
}
classpathList.removeIf(
e -> e.startsWith(utilityModule.getModuleRoot().getAbsolutePath()));
} }
// In development mode, jars do not live in module directories. Instead, each jar lives // In development mode, jars do not live in module directories. Instead, each jar lives