GT-3350: Consistently ordered classpath and ExtensionPoint priorities

This commit is contained in:
Ryan Kurtz 2019-11-22 11:35:56 -05:00
parent 7ab75e411e
commit 8f00152601
52 changed files with 341 additions and 169 deletions

View file

@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import generic.jar.ResourceFile;
import ghidra.framework.GModule;
import ghidra.util.SystemUtilities;
import utility.application.ApplicationLayout;
import utility.module.ModuleUtilities;
/**
@ -52,15 +53,17 @@ public class GhidraLauncher {
// Build the classpath
List<String> classpathList = new ArrayList<String>();
Map<String, GModule> modules = getOrderedModules(layout);
if (SystemUtilities.isInDevelopmentMode()) {
addModuleBinPaths(classpathList, layout.getModules());
addModuleBinPaths(classpathList, modules);
addExternalJarPaths(classpathList, layout.getApplicationRootDirs());
}
else {
addPatchPaths(classpathList, layout.getApplicationInstallationDir());
addModuleJarPaths(classpathList, layout.getModules());
addModuleJarPaths(classpathList, modules);
}
classpathList = orderClasspath(classpathList, layout.getModules());
classpathList = orderClasspath(classpathList, modules);
// Add the classpath to the class loader
GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader();
@ -189,6 +192,63 @@ public class GhidraLauncher {
return list;
}
/**
* Gets the modules ordered by "class-loader priority". This ensures that core modules (things
* in Framework/Features/Processors, etc) come before user modules (Extensions). It also
* guarantees a consistent module order from run to run.
*
* @param layout The layout
* @return the modules mapped by name, ordered by priority
*/
private static Map<String, GModule> getOrderedModules(ApplicationLayout layout) {
Comparator<GModule> comparator = (module1, module2) -> {
int nameComparison = module1.getName().compareTo(module2.getName());
// First handle modules that are external to the Ghidra installation.
// These should be put at the end of the list.
boolean external1 = ModuleUtilities.isExternalModule(module1, layout);
boolean external2 = ModuleUtilities.isExternalModule(module2, layout);
if (external1 && external2) {
return nameComparison;
}
if (external1) {
return -1;
}
if (external2) {
return 1;
}
// Now handle modules that are internal to the Ghidra installation.
// We will primarily order them by "type" and secondarily by name.
Map<String, Integer> typePriorityMap = Map.of(
"Framework", 0,
"Configurations", 1,
"Features", 2,
"Processors", 3,
"GPL", 4,
"Extensions", 5,
"Test", 6
);
String type1 = module1.getModuleRoot().getParentFile().getName();
String type2 = module2.getModuleRoot().getParentFile().getName();
int priority1 = typePriorityMap.getOrDefault(type1, typePriorityMap.size());
int priority2 = typePriorityMap.getOrDefault(type2, typePriorityMap.size());
if (priority1 != priority2) {
return Integer.compare(priority1, priority2);
}
return nameComparison;
};
List<GModule> moduleList = new ArrayList<>(layout.getModules().values());
Collections.sort(moduleList, comparator);
Map<String, GModule> moduleMap = new LinkedHashMap<>();
for (GModule module : moduleList) {
moduleMap.put(module.getName(), module);
}
return moduleMap;
}
/**
* Updates the list of paths to make sure the order is correct for any class-loading dependencies.
*
@ -199,18 +259,16 @@ public class GhidraLauncher {
private static List<String> orderClasspath(List<String> pathList,
Map<String, GModule> modules) {
//@formatter:off
Set<String> flatJars = modules
Set<String> fatJars = modules
.values()
.stream()
.flatMap(m -> m.getFatJars().stream())
.collect(Collectors.toSet());
//@formatter:on
List<String> orderedList = new ArrayList<String>(pathList);
for (String path : pathList) {
if (flatJars.contains(new File(path).getName())) {
if (fatJars.contains(new File(path).getName())) {
orderedList.remove(path);
orderedList.add(path);
}

View file

@ -26,6 +26,7 @@ import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException;
import utilities.util.FileUtilities;
import utility.application.ApplicationLayout;
/**
* Utility methods for module related things.
@ -334,4 +335,21 @@ public class ModuleUtilities {
}
return findRepo(f.getParentFile());
}
/**
* Checks to see if the given {@link GModule module} is external to the Ghidra installation
* directory
*
* @param module the module to check
* @param layout Ghidra's layout
* @return true if the given {@link GModule module} is external to the Ghidra installation
* directory
*/
public static boolean isExternalModule(GModule module, ApplicationLayout layout) {
File moduleRootDir = module.getModuleRoot().getFile(false);
return !layout.getApplicationRootDirs()
.stream()
.map(dir -> dir.getParentFile().getFile(false))
.anyMatch(dir -> FileUtilities.isPathContainedWithin(dir, moduleRootDir));
}
}