GP-5545_5600: ProgramLoader and more flexible loader args

This commit is contained in:
Ryan Kurtz 2025-07-01 08:18:36 -04:00
parent 4aa78ae6d0
commit d3aed2c4b3
47 changed files with 2026 additions and 976 deletions

View file

@ -20,8 +20,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadResults; import ghidra.app.util.opinion.LoadResults;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.program.model.lang.LanguageCompilerSpecPair; import ghidra.program.model.lang.LanguageCompilerSpecPair;
@ -53,21 +53,20 @@ public class ImportAllProgramsFromADirectoryScript extends GhidraScript {
continue; continue;
} }
LoadResults<Program> loadResults = null; try (LoadResults<Program> loadResults = ProgramLoader.builder()
try { .source(file)
loadResults = AutoImporter.importByLookingForLcs(file, state.getProject(), .project(state.getProject())
folder.getPathname(), language.getLanguage(), language.getCompilerSpec(), this, .projectFolderPath(folder.getPathname())
log, monitor); .language(language.getLanguage())
loadResults.getPrimary().save(state.getProject(), log, monitor); .compiler(language.getCompilerSpec())
.log(log)
.monitor(monitor)
.load()) {
loadResults.getPrimary().save(monitor);
} }
catch (IOException e) { catch (IOException e) {
println("Unable to import program from file " + file.getName()); println("Unable to import program from file " + file.getName());
} }
finally {
if (loadResults != null) {
loadResults.release(this);
}
}
println(log.toString()); println(log.toString());
log.clear(); log.clear();

View file

@ -43,8 +43,7 @@ import ghidra.app.tablechooser.TableChooserExecutor;
import ghidra.app.util.demangler.DemangledObject; import ghidra.app.util.demangler.DemangledObject;
import ghidra.app.util.demangler.DemanglerUtil; import ghidra.app.util.demangler.DemanglerUtil;
import ghidra.app.util.dialog.AskAddrDialog; import ghidra.app.util.dialog.AskAddrDialog;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.app.util.query.TableService; import ghidra.app.util.query.TableService;
import ghidra.app.util.viewer.field.BrowserCodeUnitFormat; import ghidra.app.util.viewer.field.BrowserCodeUnitFormat;
@ -3620,8 +3619,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
/** /**
* Attempts to import the specified file. It attempts to detect the format and * Attempts to import the specified file. It attempts to detect the format and
* automatically import the file. If the format is unable to be determined, then * automatically import the file. If the format is unable to be determined, then
* null is returned. For more control over the import process, {@link AutoImporter} may be * null is returned. For more control over the import process, {@link ProgramLoader} may be
* directly called. * directly used.
* <p> * <p>
* NOTE: The returned {@link Program} is not automatically saved into the current project. * NOTE: The returned {@link Program} is not automatically saved into the current project.
* <p> * <p>
@ -3634,11 +3633,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @throws Exception if any exceptions occur while importing * @throws Exception if any exceptions occur while importing
*/ */
public Program importFile(File file) throws Exception { public Program importFile(File file) throws Exception {
try { try (LoadResults<Program> loadResults = ProgramLoader.builder()
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(file, .source(file)
state.getProject(), null, this, new MessageLog(), monitor); .project(state.getProject())
loadResults.releaseNonPrimary(this); .monitor(monitor)
return loadResults.getPrimaryDomainObject(); .load()) {
return loadResults.getPrimaryDomainObject(this);
} }
catch (LoadException e) { catch (LoadException e) {
return null; return null;
@ -3647,7 +3647,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
/** /**
* Imports the specified file as raw binary. For more control over the import process, * Imports the specified file as raw binary. For more control over the import process,
* {@link AutoImporter} may be directly called. * {@link ProgramLoader} may be directly used.
* <p> * <p>
* NOTE: It is the responsibility of the script that calls this method to release the returned * NOTE: It is the responsibility of the script that calls this method to release the returned
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer * {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
@ -3661,10 +3661,15 @@ public abstract class GhidraScript extends FlatProgramAPI {
*/ */
public Program importFileAsBinary(File file, Language language, CompilerSpec compilerSpec) public Program importFileAsBinary(File file, Language language, CompilerSpec compilerSpec)
throws Exception { throws Exception {
try { try (LoadResults<Program> loadResults = ProgramLoader.builder()
Loaded<Program> loaded = AutoImporter.importAsBinary(file, state.getProject(), null, .source(file)
language, compilerSpec, this, new MessageLog(), monitor); .project(state.getProject())
return loaded.getDomainObject(); .loaders(BinaryLoader.class)
.language(language)
.compiler(compilerSpec)
.monitor(monitor)
.load()) {
return loadResults.getPrimaryDomainObject(this);
} }
catch (LoadException e) { catch (LoadException e) {
return null; return null;

View file

@ -45,8 +45,8 @@ public class AnalyzeHeadless implements GhidraLaunchable {
//@formatter:off //@formatter:off
IMPORT("-import", true, "[<directory>|<file>]+"), IMPORT("-import", true, "[<directory>|<file>]+"),
PROCESS("-process", true, "[<project_file>]"), PROCESS("-process", true, "[<project_file>]"),
PRE_SCRIPT("-prescript", true, "<ScriptName>"), PRE_SCRIPT("-preScript", true, "<ScriptName>"),
POST_SCRIPT("-postscript", true, "<ScriptName>"), POST_SCRIPT("-postScript", true, "<ScriptName>"),
SCRIPT_PATH("-scriptPath", true, "\"<path1>[;<path2>...]\""), SCRIPT_PATH("-scriptPath", true, "\"<path1>[;<path2>...]\""),
PROPERTIES_PATH("-propertiesPath", true, "\"<path1>[;<path2>...]\""), PROPERTIES_PATH("-propertiesPath", true, "\"<path1>[;<path2>...]\""),
SCRIPT_LOG("-scriptlog", true, "<path to script log file>"), SCRIPT_LOG("-scriptlog", true, "<path to script log file>"),
@ -54,7 +54,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
OVERWRITE("-overwrite", false), OVERWRITE("-overwrite", false),
RECURSIVE("-recursive", false), RECURSIVE("-recursive", false),
READ_ONLY("-readOnly", false), READ_ONLY("-readOnly", false),
DELETE_PROJECT("-deleteproject", false), DELETE_PROJECT("-deleteProject", false),
NO_ANALYSIS("-noanalysis", false), NO_ANALYSIS("-noanalysis", false),
PROCESSOR("-processor", true, "<languageID>"), PROCESSOR("-processor", true, "<languageID>"),
CSPEC("-cspec", true, "<compilerSpecID>"), CSPEC("-cspec", true, "<compilerSpecID>"),
@ -66,7 +66,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
OK_TO_DELETE("-okToDelete", false), OK_TO_DELETE("-okToDelete", false),
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"), MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"), LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"),
LOADER("-loader", true, "<desired loader name>"), LOADER(Loader.COMMAND_LINE_ARG_PREFIX, true, "<desired loader name>"),
LOADER_ARGS(Loader.COMMAND_LINE_ARG_PREFIX + "-", true, "<loader argument value>") { LOADER_ARGS(Loader.COMMAND_LINE_ARG_PREFIX + "-", true, "<loader argument value>") {
@Override @Override
public boolean matches(String arg) { public boolean matches(String arg) {
@ -424,7 +424,8 @@ public class AnalyzeHeadless implements GhidraLaunchable {
options.setPostScriptsWithArgs(postScripts); options.setPostScriptsWithArgs(postScripts);
// Set loader and loader args // Set loader and loader args
options.setLoader(loaderName, loaderArgs); options.setLoader(loaderName);
options.setLoaderArgs(loaderArgs);
// Set user-specified language and compiler spec // Set user-specified language and compiler spec
options.setLanguageAndCompiler(languageId, compilerSpecId); options.setLanguageAndCompiler(languageId, compilerSpecId);

View file

@ -31,8 +31,7 @@ import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption; import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.*;
import ghidra.framework.*; import ghidra.framework.*;
@ -1529,17 +1528,23 @@ public class HeadlessAnalyzer {
Msg.info(this, "IMPORTING: " + fsrl); Msg.info(this, "IMPORTING: " + fsrl);
LoadResults<Program> loadResults = null; // Perform the load. Note that loading 1 file may result in more than 1 thing getting
Loaded<Program> primary = null; // loaded.
try { Program primaryProgram = null;
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(fsrl)
.project(project)
.projectFolderPath(folderPath)
.language(options.language)
.compiler(options.compilerSpec)
.loaders(options.loaderClass)
.loaderArgs(options.loaderArgs)
.load()) {
// Perform the load. Note that loading 1 file may result in more than 1 thing getting
// loaded.
loadResults = loadPrograms(fsrl, folderPath);
Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files"); Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files");
primary = loadResults.getPrimary(); Loaded<Program> primary = loadResults.getPrimary();
Program primaryProgram = primary.getDomainObject(); primaryProgram = primary.getDomainObject(this);
// Make sure we are allowed to save ALL programs to the project. If not, save none and // Make sure we are allowed to save ALL programs to the project. If not, save none and
// fail. // fail.
@ -1565,7 +1570,15 @@ public class HeadlessAnalyzer {
// The act of marking the program as temporary by a script will signal // The act of marking the program as temporary by a script will signal
// us to discard any changes // us to discard any changes
if (!doSave) { if (!doSave) {
loadResults.forEach(e -> e.getDomainObject().setTemporary(true)); for (Loaded<Program> loaded : loadResults) {
Program program = loaded.getDomainObject(this);
try {
program.setTemporary(true);
}
finally {
program.release(this);
}
}
} }
// Apply saveDomainFolder to the primary program, if applicable. // Apply saveDomainFolder to the primary program, if applicable.
@ -1581,38 +1594,48 @@ public class HeadlessAnalyzer {
// Save // Save
for (Loaded<Program> loaded : loadResults) { for (Loaded<Program> loaded : loadResults) {
if (!loaded.getDomainObject().isTemporary()) { Program program = loaded.getDomainObject(this);
try { try {
DomainFile domainFile = if (!program.isTemporary()) {
loaded.save(project, new MessageLog(), TaskMonitor.DUMMY); try {
Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)", loaded, DomainFile domainFile = loaded.save(TaskMonitor.DUMMY);
domainFile)); Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)",
} loaded, domainFile));
catch (IOException e) { }
Msg.info(this, "REPORT: Save failed for: " + loaded); catch (IOException e) {
} Msg.info(this, "REPORT: Save failed for: " + loaded);
} }
else {
if (options.readOnly) {
Msg.info(this,
"REPORT: Discarded file import due to readOnly option: " + loaded);
} }
else { else {
Msg.info(this, "REPORT: Discarded file import as a result of script " + if (options.readOnly) {
"activity or analysis timeout: " + loaded); Msg.info(this,
"REPORT: Discarded file import due to readOnly option: " + loaded);
}
else {
Msg.info(this, "REPORT: Discarded file import as a result of script " +
"activity or analysis timeout: " + loaded);
}
} }
} }
finally {
program.release(this);
}
} }
// Commit changes // Commit changes
if (options.commit) { if (options.commit) {
for (Loaded<Program> loaded : loadResults) { for (Loaded<Program> loaded : loadResults) {
if (!loaded.getDomainObject().isTemporary()) { Program program = loaded.getDomainObject(this);
if (loaded == primary) { try {
AutoAnalysisManager.getAnalysisManager(primaryProgram).dispose(); if (!program.isTemporary()) {
if (loaded == primary) {
AutoAnalysisManager.getAnalysisManager(primaryProgram).dispose();
}
commitProgram(loaded.getSavedDomainFile());
} }
loaded.release(this); }
commitProgram(loaded.getSavedDomainFile()); finally {
program.release(this);
} }
} }
} }
@ -1621,7 +1644,7 @@ public class HeadlessAnalyzer {
return true; return true;
} }
catch (LoadException e) { catch (LoadException e) {
Msg.error(this, "The AutoImporter could not successfully load " + fsrl + Msg.error(this, "The ProgramLoader could not successfully load " + fsrl +
" with the provided import parameters. Please ensure that any specified" + " with the provided import parameters. Please ensure that any specified" +
" processor/cspec arguments are compatible with the loader that is used during" + " processor/cspec arguments are compatible with the loader that is used during" +
" import and try again."); " import and try again.");
@ -1638,37 +1661,12 @@ public class HeadlessAnalyzer {
return false; return false;
} }
finally { finally {
if (loadResults != null) { if (primaryProgram != null) {
loadResults.release(this); primaryProgram.release(this);
} }
} }
} }
private LoadResults<Program> loadPrograms(FSRL fsrl, String folderPath)
throws VersionException, InvalidNameException, DuplicateNameException,
CancelledException, IOException, LoadException {
MessageLog messageLog = new MessageLog();
if (options.loaderClass == null) {
// User did not specify a loader
if (options.language == null) {
return AutoImporter.importByUsingBestGuess(fsrl, project, folderPath, this,
messageLog, TaskMonitor.DUMMY);
}
return AutoImporter.importByLookingForLcs(fsrl, project, folderPath, options.language,
options.compilerSpec, this, messageLog, TaskMonitor.DUMMY);
}
// User specified a loader
if (options.language == null) {
return AutoImporter.importByUsingSpecificLoaderClass(fsrl, project, folderPath,
options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY);
}
return AutoImporter.importByUsingSpecificLoaderClassAndLcs(fsrl, project, folderPath,
options.loaderClass, options.loaderArgs, options.language, options.compilerSpec, this,
messageLog, TaskMonitor.DUMMY);
}
private void processWithImport(FSRL fsrl, String folderPath, Integer depth, boolean isFirstTime) private void processWithImport(FSRL fsrl, String folderPath, Integer depth, boolean isFirstTime)
throws IOException { throws IOException {
try (RefdFile refdFile = fsService.getRefdFile(fsrl, TaskMonitor.DUMMY)) { try (RefdFile refdFile = fsService.getRefdFile(fsrl, TaskMonitor.DUMMY)) {

View file

@ -490,30 +490,29 @@ public class HeadlessOptions {
/** /**
* Sets the loader to use for imports, as well as any loader-specific arguments. A null loader * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader
* will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" * name will attempt a "best-guess" if possible.
* is made.
* *
* @param loaderName The name (simple class name) of the loader to use. * @param loaderName The name (simple class name) of the loader to use (could be null)
* @param loaderArgs A list of loader-specific arguments. Could be null if there are none. * @throws InvalidInputException if an invalid loader name was specified
* @throws InvalidInputException if an invalid loader name was specified, or if loader arguments
* were specified but a loader was not.
*/ */
public void setLoader(String loaderName, List<Pair<String, String>> loaderArgs) public void setLoader(String loaderName) throws InvalidInputException {
throws InvalidInputException {
if (loaderName != null) { if (loaderName != null) {
this.loaderClass = LoaderService.getLoaderClassByName(loaderName); this.loaderClass = LoaderService.getLoaderClassByName(loaderName);
if (this.loaderClass == null) { if (this.loaderClass == null) {
throw new InvalidInputException("Invalid loader name specified: " + loaderName); throw new InvalidInputException("Invalid loader name specified: " + loaderName);
} }
this.loaderArgs = loaderArgs;
} }
else { else {
if (loaderArgs != null && loaderArgs.size() > 0) {
throw new InvalidInputException(
"Loader arguments defined without a loader being specified.");
}
this.loaderClass = null; this.loaderClass = null;
this.loaderArgs = null;
} }
} }
/**
* Sets the loader arguments
*
* @param loaderArgs A list of loader-specific arguments. Could be null if there are none.
*/
public void setLoaderArgs(List<Pair<String, String>> loaderArgs) {
this.loaderArgs = loaderArgs;
}
} }

View file

@ -17,28 +17,27 @@ package ghidra.app.util.importer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import generic.stl.Pair; import generic.stl.Pair;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.FSRL; import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
* Utility methods to do {@link Program} imports automatically (without requiring user interaction) * Utility methods to do {@link Program} imports automatically (without requiring user interaction)
*
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public final class AutoImporter { public final class AutoImporter {
private AutoImporter() { private AutoImporter() {
// service class; cannot instantiate // service class; cannot instantiate
@ -50,10 +49,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param file The {@link File} to import * @param file The {@link File} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -64,10 +63,12 @@ public final class AutoImporter {
* reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results
* should be queried for their true project folder paths using * should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -76,13 +77,20 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingBestGuess(File file, Project project, public static LoadResults<Program> importByUsingBestGuess(File file, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor) String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException, throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException { VersionException, LoadException {
return importByUsingBestGuess(fileToFsrl(file), project, projectFolderPath, consumer, return ProgramLoader.builder()
messageLog, monitor); .source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -91,10 +99,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param fsrl The {@link FSRL} to import * @param fsrl The {@link FSRL} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -105,10 +113,12 @@ public final class AutoImporter {
* reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results
* should be queried for their true project folder paths using * should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -117,14 +127,20 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingBestGuess(FSRL fsrl, Project project, public static LoadResults<Program> importByUsingBestGuess(FSRL fsrl, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor) String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException, throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException { VersionException, LoadException {
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, return ProgramLoader.builder()
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, .source(fsrl)
OptionChooser.DEFAULT_OPTIONS); .project(project)
.projectFolderPath(projectFolderPath)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -133,10 +149,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param provider The bytes to import * @param provider The bytes to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -147,10 +163,12 @@ public final class AutoImporter {
* reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results
* should be queried for their true project folder paths using * should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -159,14 +177,20 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingBestGuess(ByteProvider provider, public static LoadResults<Program> importByUsingBestGuess(ByteProvider provider,
Project project, String projectFolderPath, Object consumer, MessageLog messageLog, Project project, String projectFolderPath, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException { InvalidNameException, VersionException, LoadException {
return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor, return ProgramLoader.builder()
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, .source(provider)
OptionChooser.DEFAULT_OPTIONS); .project(project)
.projectFolderPath(projectFolderPath)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -174,10 +198,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param file The {@link File} to import * @param file The {@link File} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -190,10 +214,12 @@ public final class AutoImporter {
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param loaderClass The {@link Loader} class to use * @param loaderClass The {@link Loader} class to use
* @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -202,14 +228,23 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClass(File file, public static LoadResults<Program> importByUsingSpecificLoaderClass(File file,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass, Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog, List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException { InvalidNameException, VersionException, LoadException {
return importByUsingSpecificLoaderClass(fileToFsrl(file), project, projectFolderPath, return ProgramLoader.builder()
loaderClass, loaderArgs, consumer, messageLog, monitor); .source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.loaders(loaderClass)
.loaderArgs(loaderArgs)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -217,10 +252,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param fsrl The {@link FSRL} to import * @param fsrl The {@link FSRL} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -233,10 +268,12 @@ public final class AutoImporter {
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param loaderClass The {@link Loader} class to use * @param loaderClass The {@link Loader} class to use
* @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -245,16 +282,23 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClass(FSRL fsrl, Project project, public static LoadResults<Program> importByUsingSpecificLoaderClass(FSRL fsrl, Project project,
String projectFolderPath, Class<? extends Loader> loaderClass, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog, List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException { InvalidNameException, VersionException, LoadException {
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs); return ProgramLoader.builder()
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, .source(fsrl)
loaderFilter, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, .project(project)
new LoaderArgsOptionChooser(loaderFilter)); .projectFolderPath(projectFolderPath)
.loaders(loaderClass)
.loaderArgs(loaderArgs)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -263,10 +307,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param file The {@link File} to import * @param file The {@link File} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -279,10 +323,12 @@ public final class AutoImporter {
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language} * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification} * @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -291,13 +337,22 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByLookingForLcs(File file, Project project, public static LoadResults<Program> importByLookingForLcs(File file, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException { DuplicateNameException, InvalidNameException, VersionException, LoadException {
return importByLookingForLcs(fileToFsrl(file), project, projectFolderPath, language, return ProgramLoader.builder()
compilerSpec, consumer, messageLog, monitor); .source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -306,10 +361,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param fsrl The {@link FSRL} to import * @param fsrl The {@link FSRL} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -322,10 +377,12 @@ public final class AutoImporter {
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language} * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification} * @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -334,14 +391,22 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByLookingForLcs(FSRL fsrl, Project project, public static LoadResults<Program> importByLookingForLcs(FSRL fsrl, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException { DuplicateNameException, InvalidNameException, VersionException, LoadException {
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, return ProgramLoader.builder()
LoaderService.ACCEPT_ALL, new LcsHintLoadSpecChooser(language, compilerSpec), null, .source(fsrl)
OptionChooser.DEFAULT_OPTIONS); .project(project)
.projectFolderPath(projectFolderPath)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -350,10 +415,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param file The {@link File} to import * @param file The {@link File} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -368,10 +433,12 @@ public final class AutoImporter {
* @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments
* @param language The desired {@link Language} * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification} * @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -379,14 +446,25 @@ public final class AutoImporter {
* @throws InvalidNameException if an invalid {@link Program} name was used during load * @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(File file, public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(File file,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass, Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec, List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { CancelledException, DuplicateNameException, InvalidNameException, VersionException {
return importByUsingSpecificLoaderClassAndLcs(fileToFsrl(file), project, projectFolderPath, return ProgramLoader.builder()
loaderClass, loaderArgs, language, compilerSpec, consumer, messageLog, monitor); .source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.loaders(loaderClass)
.loaderArgs(loaderArgs)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -395,10 +473,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param fsrl The {@link FSRL} to import * @param fsrl The {@link FSRL} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -413,10 +491,12 @@ public final class AutoImporter {
* @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments
* @param language The desired {@link Language} * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification} * @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -424,31 +504,37 @@ public final class AutoImporter {
* @throws InvalidNameException if an invalid {@link Program} name was used during load * @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(FSRL fsrl, public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(FSRL fsrl,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass, Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec, List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { CancelledException, DuplicateNameException, InvalidNameException, VersionException {
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs); return ProgramLoader.builder()
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, .source(fsrl)
loaderFilter, new LcsHintLoadSpecChooser(language, compilerSpec), null, .project(project)
new LoaderArgsOptionChooser(loaderFilter)); .projectFolderPath(projectFolderPath)
.loaders(loaderClass)
.loaderArgs(loaderArgs)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
private static final Predicate<Loader> BINARY_LOADER =
new SingleLoaderFilter(BinaryLoader.class);
/** /**
* Automatically imports the given {@link File} with the {@link BinaryLoader}, using the given * Automatically imports the given {@link File} with the {@link BinaryLoader}, using the given
* language and compiler specification. * language and compiler specification.
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program} is * Note that when the import completes, the returned {@link Loaded} {@link Program} is
* not saved to a project. That is the responsibility of the caller (see * not saved to a project. That is the responsibility of the caller (see
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}). * {@link Loaded#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program} with {@link Loaded#release(Object)} when it is no longer needed. * {@link Program} with {@link Loaded#close()} when it is no longer needed.
* *
* @param file The {@link File} to import * @param file The {@link File} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -461,7 +547,9 @@ public final class AutoImporter {
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language} * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification} * @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link Loaded}
* {@link Program}, used to ensure the underlying {@link Program} is only closed when every
* consumer is done with it (see {@link Loaded#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link Loaded} {@link Program} (created but not saved) * @return The {@link Loaded} {@link Program} (created but not saved)
@ -472,15 +560,24 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static Loaded<Program> importAsBinary(File file, Project project, public static Loaded<Program> importAsBinary(File file, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException { DuplicateNameException, InvalidNameException, VersionException, LoadException {
LoadResults<Program> loadResults = importFresh(file, project, projectFolderPath, consumer, LoadResults<Program> loadResults = ProgramLoader.builder()
messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec), .source(file)
null, OptionChooser.DEFAULT_OPTIONS); .project(project)
loadResults.releaseNonPrimary(consumer); .projectFolderPath(projectFolderPath)
.loaders(BinaryLoader.class)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
loadResults.getNonPrimary().forEach(Loaded::close);
return loadResults.getPrimary(); return loadResults.getPrimary();
} }
@ -490,10 +587,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program} is * Note that when the import completes, the returned {@link Loaded} {@link Program} is
* not saved to a project. That is the responsibility of the caller (see * not saved to a project. That is the responsibility of the caller (see
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}). * {@link Loaded#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program} with {@link Loaded#release(Object)} when it is no longer needed. * {@link Program} with {@link Loaded#close()} when it is no longer needed.
* *
* @param bytes The bytes to import * @param bytes The bytes to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -506,7 +603,9 @@ public final class AutoImporter {
* {@link Loaded#getProjectFolderPath()}. * {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language} * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification} * @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link Loaded}
* {@link Program}, used to ensure the underlying {@link Program} is only closed when every
* consumer is done with it (see {@link Loaded#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link Loaded} {@link Program} (created but not saved) * @return The {@link Loaded} {@link Program} (created but not saved)
@ -517,16 +616,25 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static Loaded<Program> importAsBinary(ByteProvider bytes, Project project, public static Loaded<Program> importAsBinary(ByteProvider bytes, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec, String projectFolderPath, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException, CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException { LoadException {
LoadResults<Program> loadResults = importFresh(bytes, project, projectFolderPath, consumer, LoadResults<Program> loadResults = ProgramLoader.builder()
messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec), .source(bytes)
null, OptionChooser.DEFAULT_OPTIONS); .project(project)
loadResults.releaseNonPrimary(consumer); .projectFolderPath(projectFolderPath)
.loaders(BinaryLoader.class)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
loadResults.getNonPrimary().forEach(Loaded::close);
return loadResults.getPrimary(); return loadResults.getPrimary();
} }
@ -535,10 +643,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param file The {@link File} to import * @param file The {@link File} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -556,10 +664,12 @@ public final class AutoImporter {
* {@link Loader}'s preferred name. * {@link Loader}'s preferred name.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get * @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* used * used
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -568,15 +678,27 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importFresh(File file, Project project, public static LoadResults<Program> importFresh(File file, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser, Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser) throws IOException, String importNameOverride, OptionChooser optionChooser) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException, CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException { LoadException {
return importFresh(fileToFsrl(file), project, projectFolderPath, consumer, messageLog, return ProgramLoader.builder()
monitor, loaderFilter, loadSpecChooser, importNameOverride, optionChooser); .source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.name(importNameOverride)
.loaders(loaderFilter)
.loaderArgs(optionChooser.getArgs())
.language(loadSpecChooser.getLanguageId())
.compiler(loadSpecChooser.getCompilerSpecId())
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -584,10 +706,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param fsrl The {@link FSRL} to import * @param fsrl The {@link FSRL} to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -605,10 +727,12 @@ public final class AutoImporter {
* {@link Loader}'s preferred name. * {@link Loader}'s preferred name.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get * @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* used * used
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -617,22 +741,27 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importFresh(FSRL fsrl, Project project, public static LoadResults<Program> importFresh(FSRL fsrl, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser, Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser) String importNameOverride, OptionChooser optionChooser)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException, throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException { VersionException, LoadException {
if (fsrl == null) { return ProgramLoader.builder()
throw new LoadException("Cannot load null fsrl"); .source(fsrl)
} .project(project)
.projectFolderPath(projectFolderPath)
try (ByteProvider provider = .name(importNameOverride)
FileSystemService.getInstance().getByteProvider(fsrl, true, monitor)) { .loaders(loaderFilter)
return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor, .loaderArgs(optionChooser.getArgs())
loaderFilter, loadSpecChooser, importNameOverride, optionChooser); .language(loadSpecChooser.getLanguageId())
} .compiler(loadSpecChooser.getCompilerSpecId())
.log(messageLog)
.monitor(monitor)
.load(consumer);
} }
/** /**
@ -640,10 +769,10 @@ public final class AutoImporter {
* <p> * <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. * {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
* *
* @param provider The bytes to import * @param provider The bytes to import
* @param project The {@link Project}. Loaders can use this to take advantage of existing * @param project The {@link Project}. Loaders can use this to take advantage of existing
@ -661,10 +790,12 @@ public final class AutoImporter {
* {@link Loader}'s preferred name. * {@link Loader}'s preferred name.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get * @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* used * used
* @param consumer A consumer * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param messageLog The log * @param messageLog The log
* @param monitor A task monitor * @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s
* (created but not saved) * (created but not saved)
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -673,103 +804,26 @@ public final class AutoImporter {
* @throws VersionException if there was an issue with database versions, probably due to a * @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade * failed language upgrade
* @throws LoadException if nothing was loaded * @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importFresh(ByteProvider provider, Project project, public static LoadResults<Program> importFresh(ByteProvider provider, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser, Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser) throws IOException, String importNameOverride, OptionChooser optionChooser) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException, CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException { LoadException {
return ProgramLoader.builder()
if (provider == null) { .source(provider)
throw new LoadException("Cannot load null provider"); .project(project)
} .projectFolderPath(projectFolderPath)
.name(importNameOverride)
// Get the load spec .loaders(loaderFilter)
LoadSpec loadSpec = getLoadSpec(loaderFilter, loadSpecChooser, provider); .loaderArgs(optionChooser.getArgs())
if (loadSpec == null) { .language(loadSpecChooser.getLanguageId())
throw new LoadException("No load spec found"); .compiler(loadSpecChooser.getCompilerSpecId())
} .log(messageLog)
.monitor(monitor)
// Get the preferred import name .load(consumer);
String importName = loadSpec.getLoader().getPreferredFileName(provider);
if (importNameOverride != null) {
importName = importNameOverride;
}
// Collect options
LanguageCompilerSpecPair languageCompilerSpecPair = loadSpec.getLanguageCompilerSpec();
AddressFactory addrFactory = null;// Address type options not permitted if null
if (languageCompilerSpecPair != null) {
// It is assumed that if languageCompilerSpecPair exists, then language will be found
addrFactory = DefaultLanguageService.getLanguageService()
.getLanguage(
languageCompilerSpecPair.languageID)
.getAddressFactory();
}
List<Option> loaderOptions = optionChooser.choose(
loadSpec.getLoader().getDefaultOptions(provider, loadSpec, null, false), addrFactory);
if (loaderOptions == null) {
throw new LoadException("Cannot load with null options");
}
// Import
Msg.info(AutoImporter.class, "Using Loader: " + loadSpec.getLoader().getName());
Msg.info(AutoImporter.class,
"Using Language/Compiler: " + loadSpec.getLanguageCompilerSpec());
Msg.info(AutoImporter.class, "Using Library Search Path: " +
Arrays.toString(LibrarySearchPathManager.getLibraryPaths()));
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(provider, importName, project, projectFolderPath, loadSpec, loaderOptions,
messageLog, consumer, monitor);
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && messageLog.hasMessages()) {
Msg.info(AutoImporter.class, "Additional info:\n" + messageLog.toString());
}
// Filter out and release non-Programs
List<Loaded<Program>> loadedPrograms = new ArrayList<>();
for (Loaded<? extends DomainObject> loaded : loadResults) {
if (loaded.getDomainObject() instanceof Program program) {
loadedPrograms.add(
new Loaded<Program>(program, loaded.getName(), loaded.getProjectFolderPath()));
}
else {
loaded.release(consumer);
}
}
if (loadedPrograms.isEmpty()) {
throw new LoadException("Domain objects were loaded, but none were Programs");
}
return new LoadResults<>(loadedPrograms);
}
private static LoadSpec getLoadSpec(Predicate<Loader> loaderFilter,
LoadSpecChooser loadSpecChooser, ByteProvider provider) {
LoaderMap loaderMap = LoaderService.getSupportedLoadSpecs(provider, loaderFilter);
LoadSpec loadSpec = loadSpecChooser.choose(loaderMap);
if (loadSpec != null) {
return loadSpec;
}
File f = provider.getFile();
String name = f != null ? f.getAbsolutePath() : provider.getName();
Msg.info(AutoImporter.class, "No load spec found for import file: " + name);
return null;
}
/**
* Converts a {@link File} to a local file system {@link FSRL}
* @param file The {@link File} to convert
* @return A {@link FSRL} that represents the given {@link File}
* @throws LoadException if the given {@link File} is null
*/
private static FSRL fileToFsrl(File file) throws LoadException {
if (file == null) {
throw new LoadException("Cannot load null file");
}
return FileSystemService.getInstance().getLocalFSRL(file);
} }
} }

View file

@ -43,6 +43,11 @@ public class CsHintLoadSpecChooser implements LoadSpecChooser {
this(new CompilerSpecID(compilerSpecID)); this(new CompilerSpecID(compilerSpecID));
} }
@Override
public CompilerSpecID getCompilerSpecId() {
return compilerSpecID;
}
@Override @Override
public LoadSpec choose(LoaderMap loaderMap) { public LoadSpec choose(LoaderMap loaderMap) {

View file

@ -16,9 +16,11 @@
package ghidra.app.util.importer; package ghidra.app.util.importer;
import java.util.Collection; import java.util.Collection;
import java.util.Objects;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.Msg; import ghidra.util.Msg;
import util.CollectionUtils; import util.CollectionUtils;
@ -37,13 +39,46 @@ public class LcsHintLoadSpecChooser implements LoadSpecChooser {
* {@link CompilerSpec}. * {@link CompilerSpec}.
* *
* @param language The {@link Language} to use (should not be null) * @param language The {@link Language} to use (should not be null)
* @param compilerSpec The {@link CompilerSpec} to use (f null default compiler spec will be used) * @param compilerSpec The {@link CompilerSpec} to use (if null default compiler spec will be
* used)
* @throws LanguageNotFoundException if there was a problem getting the language
*/ */
public LcsHintLoadSpecChooser(Language language, CompilerSpec compilerSpec) { public LcsHintLoadSpecChooser(Language language, CompilerSpec compilerSpec)
this.languageID = language.getLanguageID(); throws LanguageNotFoundException {
this.compilerSpecID = this(language.getLanguageID(),
(compilerSpec == null) ? language.getDefaultCompilerSpec().getCompilerSpecID() compilerSpec == null ? language.getDefaultCompilerSpec().getCompilerSpecID()
: compilerSpec.getCompilerSpecID(); : compilerSpec.getCompilerSpecID());
}
/**
* Creates a new {@link LcsHintLoadSpecChooser}.
* <p>
* NOTE: It is assumed that the given {@link LanguageID} is valid and it supports the given
* {@link CompilerSpecID}.
*
* @param languageId The {@link LanguageID} to use (should not be null)
* @param compilerSpecId The {@link CompilerSpecID} to use (if null default compiler spec will
* be used)
* @throws LanguageNotFoundException if there was a problem getting the language
*/
public LcsHintLoadSpecChooser(LanguageID languageId, CompilerSpecID compilerSpecId)
throws LanguageNotFoundException {
this.languageID = languageId;
this.compilerSpecID = Objects.requireNonNullElse(compilerSpecId,
DefaultLanguageService.getLanguageService()
.getLanguage(languageID)
.getDefaultCompilerSpec()
.getCompilerSpecID());
}
@Override
public LanguageID getLanguageId() {
return languageID;
}
@Override
public CompilerSpecID getCompilerSpecId() {
return compilerSpecID;
} }
@Override @Override

View file

@ -16,6 +16,8 @@
package ghidra.app.util.importer; package ghidra.app.util.importer;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
/** /**
* Chooses a {@link LoadSpec} for a {@link Loader} to use based on some criteria * Chooses a {@link LoadSpec} for a {@link Loader} to use based on some criteria
@ -31,6 +33,26 @@ public interface LoadSpecChooser {
*/ */
public LoadSpec choose(LoaderMap loaderMap); public LoadSpec choose(LoaderMap loaderMap);
/**
* Gets the desired {@link LanguageID} associated with this chooser
*
* @return the desired {@link LanguageID} associated with this chooser, or {@code null} to mean
* "any"
*/
public default LanguageID getLanguageId() {
return null;
}
/**
* Gets the desired {@link CompilerSpecID} associated with this chooser
*
* @return the desired {@link CompilerSpecID} associated with this chooser, or {@code null} to
* mean "any"
*/
public default CompilerSpecID getCompilerSpecId() {
return null;
}
/** /**
* Chooses the first "preferred" {@link LoadSpec} * Chooses the first "preferred" {@link LoadSpec}
* *

View file

@ -19,18 +19,29 @@ import java.util.List;
import generic.stl.Pair; import generic.stl.Pair;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.opinion.Loader;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressFactory;
import ghidra.util.Msg; import ghidra.util.Msg;
/** /**
* An option chooser that applies loader options that were passed in as command line arguments. * An option chooser that applies loader options that were passed in as command line arguments.
*
* @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public class LoaderArgsOptionChooser implements OptionChooser { public class LoaderArgsOptionChooser implements OptionChooser {
private List<Pair<String, String>> loaderArgs; private List<Pair<String, String>> loaderArgs;
public LoaderArgsOptionChooser(SingleLoaderFilter loaderFilter) { /**
this.loaderArgs = loaderFilter.getLoaderArgs(); * Creates a new {@link LoaderArgsOptionChooser}
*
* @param loaderArgs The {@link Loader} arguments
* @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
public LoaderArgsOptionChooser(List<Pair<String, String>> loaderArgs) {
this.loaderArgs = loaderArgs;
} }
@Override @Override
@ -43,12 +54,12 @@ public class LoaderArgsOptionChooser implements OptionChooser {
if (option.getArg() != null && arg.equalsIgnoreCase(option.getArg())) { if (option.getArg() != null && arg.equalsIgnoreCase(option.getArg())) {
Object oldVal = option.getValue(); Object oldVal = option.getValue();
if (option.parseAndSetValueByType(val, addressFactory)) { if (option.parseAndSetValueByType(val, addressFactory)) {
Msg.info(AutoImporter.class, String.format( Msg.info(LoaderArgsOptionChooser.class, String.format(
"Successfully applied \"%s\" to \"%s\" (old: \"%s\", new: \"%s\")", "Successfully applied \"%s\" to \"%s\" (old: \"%s\", new: \"%s\")",
arg, option.getName(), oldVal, val)); arg, option.getName(), oldVal, val));
} }
else { else {
Msg.error(AutoImporter.class, String.format( Msg.error(LoaderArgsOptionChooser.class, String.format(
"Failed to apply \"%s\" to \"%s\" (old: \"%s\", bad: \"%s\")", arg, "Failed to apply \"%s\" to \"%s\" (old: \"%s\", bad: \"%s\")", arg,
option.getName(), oldVal, val)); option.getName(), oldVal, val));
return null; return null;
@ -58,11 +69,16 @@ public class LoaderArgsOptionChooser implements OptionChooser {
} }
} }
if (!foundIt) { if (!foundIt) {
Msg.error(AutoImporter.class, "Loader does not support " + arg + " argument"); Msg.warn(LoaderArgsOptionChooser.class,
return null; "Skipping unsupported " + arg + " argument");
} }
} }
} }
return optionChoices; return optionChoices;
} }
@Override
public List<Pair<String, String>> getArgs() {
return loaderArgs;
}
} }

View file

@ -17,11 +17,41 @@ package ghidra.app.util.importer;
import java.util.List; import java.util.List;
import generic.stl.Pair;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.opinion.Loader;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressFactory;
/**
* Chooses which {@link Loader} options to use
*
* @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
@FunctionalInterface @FunctionalInterface
public interface OptionChooser { public interface OptionChooser {
public static final OptionChooser DEFAULT_OPTIONS = (choices, addressFactory) -> choices; public static final OptionChooser DEFAULT_OPTIONS = (choices, addressFactory) -> choices;
/**
* Chooses which {@link Loader} options to use
*
* @param optionChoices A {@link List} of available {@link Loader} options
* @param addressFactory The address factory
* @return The {@link List} of {@link Loader} options to use
* @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
List<Option> choose(List<Option> optionChoices, AddressFactory addressFactory); List<Option> choose(List<Option> optionChoices, AddressFactory addressFactory);
/**
* Gets the {@link Loader} arguments associated with this {@link OptionChooser}
*
* @return The {@link Loader} arguments associated with this {@link OptionChooser}
* @throws UnsupportedOperationException if a subclass has not implemented this method
* @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
public default List<Pair<String, String>> getArgs() {
throw new UnsupportedOperationException();
}
} }

View file

@ -0,0 +1,582 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.importer;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import generic.stl.Pair;
import ghidra.app.util.Option;
import ghidra.app.util.bin.*;
import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.model.*;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Used to load (import) a new {@link Program}
*/
public class ProgramLoader {
/**
* Gets a new {@link ProgramLoader} {@link Builder} which can be used to load a new
* {@link Program}
*
* @return A new {@link ProgramLoader} {@link Builder} which can be used to load a new
* {@link Program}
*/
public static Builder builder() {
return new Builder();
}
/**
* A class to configure and perform a {@link Program} load
*/
public static class Builder {
private ByteProvider provider;
private FSRL fsrl;
private File file;
private byte[] bytes;
private Project project;
private String projectFolderPath;
private String importNameOverride;
private Predicate<Loader> loaderFilter = LoaderService.ACCEPT_ALL;
private List<Pair<String, String>> loaderArgs = new ArrayList<>();
private LanguageID languageId;
private CompilerSpecID compilerSpecId;
private MessageLog log = new MessageLog();
private TaskMonitor monitor = TaskMonitor.DUMMY;
/**
* Create a new {@link Builder}. Not intended to be used outside of {@link ProgramLoader}.
*/
private Builder() {
// Prevent public instantiation
}
/**
* Sets the required import source to the given {@link ByteProvider}.
* <p>
* NOTE: Any previously defined sources will be overwritten.
* <p>
* NOTE: Ownership of the given {@link ByteProvider} is not transfered to this
* {@link Builder}, so it is the responsibility of the caller to properly
* {@link ByteProvider#close() close} it when done.
*
* @param p The {@link ByteProvider} to import. A {@code null} value will unset the source.
* @return This {@link Builder}
*/
public Builder source(ByteProvider p) {
this.provider = p;
this.fsrl = null;
this.file = null;
this.bytes = null;
return this;
}
/**
* Sets the required import source to the given {@link FSRL}
* <p>
* NOTE: Any previously defined sources will be overwritten
*
* @param f The {@link FSRL} to import. A {@code null} value will unset the source.
* @return This {@link Builder}
*/
public Builder source(FSRL f) {
this.provider = null;
this.fsrl = f;
this.file = null;
this.bytes = null;
return this;
}
/**
* Sets the required import source to the given {@link File}
* <p>
* NOTE: Any previously defined sources will be overwritten
*
* @param f The {@link File} to import. A {@code null} value will unset the source.
* @return This {@link Builder}
*/
public Builder source(File f) {
this.provider = null;
this.fsrl = null;
this.file = f;
this.bytes = null;
return this;
}
/**
* Sets the required import source to the given bytes
* <p>
* NOTE: Any previously defined sources will be overwritten
*
* @param b The bytes to import. A {@code null} value will unset the source.
* @return This {@link Builder}
*/
public Builder source(byte[] b) {
this.provider = null;
this.fsrl = null;
this.file = null;
this.bytes = b;
return this;
}
/**
* Sets the {@link Project}. Loaders can use this to take advantage of existing
* {@link DomainFolder}s and {@link DomainFile}s to do custom behaviors such as loading
* libraries.
* <p>
* By default, no {@link Project} is associated with the {@link ProgramLoader}.
*
* @param p The {@link Project}. A {@code null} value will unset the project.
* @return This {@link Builder}
*/
public Builder project(Project p) {
this.project = p;
return this;
}
/**
* Sets the suggested project folder path for the {@link Loaded} {@link Program}s. This is
* just a suggestion, and a {@link Loader} implementation reserves the right to change it
* for each {@link Loaded} result. The {@link Loaded} results should be queried for their
* true project folder paths using {@link Loaded#getProjectFolderPath()}.
* <p>
* The default project folder path is the root of the project ({@code "/"}).
*
* @param path The suggested project folder path. A {@code null} value will revert the path
* back to the default value of ({@code "/"}).
* @return This {@link Builder}
*/
public Builder projectFolderPath(String path) {
this.projectFolderPath = path;
return this;
}
/**
* Sets the name to use for the imported {@link Program}.
* <p>
* The default is the {@link Loader}'s preferred name.
*
* @param name The name to use for the imported {@link Program}. A {@code null} value will
* revert the name to the {@link Loader}'s preferred name.
* @return This {@link Builder}
*/
public Builder name(String name) {
this.importNameOverride = name;
return this;
}
/**
* Sets the acceptable {@link Loader}s to use during import.
* <p>
* By default, all {@link Loader}s are accepted ({@link LoaderService#ACCEPT_ALL}).
*
* @param filter A filter used to limit the {@link Loader}s used during import. A
* {@code null} value will revert back to the default ({@link LoaderService#ACCEPT_ALL}).
* @return This {@link Builder}
*/
public Builder loaders(Predicate<Loader> filter) {
this.loaderFilter = filter != null ? filter : LoaderService.ACCEPT_ALL;
return this;
}
/**
* Sets the acceptable {@link Loader} to use during import.
* <p>
* By default, all {@link Loader}s are accepted ({@link LoaderService#ACCEPT_ALL}).
*
* @param cls The class of the {@link Loader} to use during import. A {@code null} value
* will revert back to the default ({@link LoaderService#ACCEPT_ALL}).
* @return This {@link Builder}
*/
public Builder loaders(Class<? extends Loader> cls) {
this.loaderFilter =
cls != null ? loader -> loader.getClass().equals(cls) : LoaderService.ACCEPT_ALL;
return this;
}
/**
* Sets the {@link Loader}s to use during import.
* <p>
* By default, all {@link Loader}s are accepted ({@link LoaderService#ACCEPT_ALL}).
*
* @param cls A {@link List} of classes of {@link Loader}s to use during import. A
* {@code null} value will revert back to the default ({@link LoaderService#ACCEPT_ALL}).
* @return This {@link Builder}
*/
public Builder loaders(List<Class<? extends Loader>> cls) {
this.loaderFilter =
cls != null ? loader -> cls.contains(loader.getClass()) : LoaderService.ACCEPT_ALL;
return this;
}
/**
* Sets the {@link Loader} arguments to use during import.
* <p>
* By default, no {@link Loader} arguments are used.
*
* @param args A {@link List} of {@link Loader} argument name/value {@link Pair}s to use
* during import. A {@code null} value will result in no {@link Loader} arguments being
* used.
* @return This {@link Builder}
*/
public Builder loaderArgs(List<Pair<String, String>> args) {
this.loaderArgs = args != null ? new ArrayList<>(args) : new ArrayList<>();
return this;
}
/**
* Adds the given {@link Loader} argument to use during import.
*
* @param name A single {@link Loader} argument name to use during import.
* @param value The value that corresponds to the argument {@code name}
* @return This {@link Builder}
*/
public Builder addLoaderArg(String name, String value) {
this.loaderArgs.add(new Pair<String, String>(name, value));
return this;
}
/**
* Sets the language to use during import.
* <p>
* By default, the first "preferred" language is used.
*
* @param id The language id to use during import. A {@code null} value will result in the
* first "preferred" language being used.
* @return This {@link Builder}
*/
public Builder language(String id) {
this.languageId = id != null ? new LanguageID(id) : null;
return this;
}
/**
* Sets the language to use during import.
* <p>
* By default, the first "preferred" language is used.
*
* @param id The {@link LanguageID} to use during import. A {@code null} value will result
* in the first "preferred" language being used.
* @return This {@link Builder}
*/
public Builder language(LanguageID id) {
this.languageId = id;
return this;
}
/**
* Sets the language to use during import.
* <p>
* By default, the first "preferred" language is used.
*
* @param language The {@link Language} to use during import. A {@code null} value will
* result in the first "preferred" language being used.
* @return This {@link Builder}
*/
public Builder language(Language language) {
this.languageId = language != null ? language.getLanguageID() : null;
return this;
}
/**
* Sets the compiler to use during import.
* <p>
* By default, the processor's default compiler is used.
*
* @param id The compiler spec id to use during import. A {@code null} value will result in
* the language's default compiler being used.
* @return This {@link Builder}
*/
public Builder compiler(String id) {
this.compilerSpecId = id != null ? new CompilerSpecID(id) : null;
return this;
}
/**
* Sets the compiler to use during import.
* <p>
* By default, the processor's default compiler is used.
*
* @param id The {@link CompilerSpecID} to use during import. A {@code null} value will
* result in the language's default compiler being used.
* @return This {@link Builder}
*/
public Builder compiler(CompilerSpecID id) {
this.compilerSpecId = id;
return this;
}
/**
* Sets the compiler to use during import.
* <p>
* By default, the processor's default compiler is used.
*
* @param cspec The {@link CompilerSpec} to use during import. A {@code null} value will
* result in the language's default compiler being used.
* @return This {@link Builder}
*/
public Builder compiler(CompilerSpec cspec) {
this.compilerSpecId = cspec != null ? cspec.getCompilerSpecID() : null;
return this;
}
/**
* Sets the {@link MessageLog log} to use during import.
* <p>
* By default, no log is used.
*
* @param messageLog The {@link MessageLog log} to use during import. A {@code null} value
* will result in not logging.
* @return This {@link Builder}
*/
public Builder log(MessageLog messageLog) {
this.log = messageLog;
return this;
}
/**
* Sets the {@link TaskMonitor} to use during import.
* <p>
* By default, {@link TaskMonitor#DUMMY} is used.
*
* @param mon The {@link TaskMonitor} to use during import. A {@code null} value will result
* in {@link TaskMonitor#DUMMY} being used.
* @return This {@link Builder}
*/
public Builder monitor(TaskMonitor mon) {
this.monitor = mon;
return this;
}
/**
* Loads the specified {@link #source(ByteProvider) source} with this {@link Builder}'s
* current configuration
*
* @return The {@link LoadResults} which contains one or more {@link Loaded}
* {@link Program}s (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
*/
public LoadResults<Program> load() throws IOException, LanguageNotFoundException,
CancelledException, VersionException, LoadException {
return load(this);
}
/**
* Loads the specified {@link #source(ByteProvider) source} with this {@link Builder}'s
* current configuration.
* <p>
* NOTE: This method exists to maintain compatibility with the {@link AutoImporter} class,
* whose methods require consumer objects to be passed in. It should not be used by clients
* (use {@link #load()} instead, which uses a built-in consumer).
*
* @param consumer A reference to the object "consuming" the returned {@link LoadResults},
* used to ensure the underlying {@link Program}s are only closed when every consumer is
* done with it (see {@link LoadResults#close()}).
* @return The {@link LoadResults} which contains one or more {@link Loaded}
* {@link Program}s (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link #load()} instead
*/
@SuppressWarnings("unchecked")
@Deprecated(since = "11.5", forRemoval = true)
LoadResults<Program> load(Object consumer) throws IOException, LanguageNotFoundException,
CancelledException, VersionException, LoadException {
try (ByteProvider p = getSourceAsProvider()) {
LoadSpec loadSpec = getLoadSpec(p);
List<Option> loaderOptions = getLoaderOptions(p, loadSpec);
String importName = Objects.requireNonNullElse(importNameOverride,
loadSpec.getLoader().getPreferredFileName(p));
// Load
Msg.info(ProgramLoader.class, "Using Loader: " + loadSpec.getLoader().getName());
Msg.info(ProgramLoader.class,
"Using Language/Compiler: " + loadSpec.getLanguageCompilerSpec());
Msg.info(ProgramLoader.class, "Using Library Search Path: " +
Arrays.toString(LibrarySearchPathManager.getLibraryPaths()));
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(p, importName, project, projectFolderPath, loadSpec, loaderOptions,
log, Objects.requireNonNullElse(consumer, this), monitor);
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && log.hasMessages()) {
Msg.info(ProgramLoader.class, "Additional info:\n" + log);
}
// Filter out and release non-Programs
List<Loaded<Program>> loadedPrograms = new ArrayList<>();
for (Loaded<? extends DomainObject> loaded : loadResults) {
if (Program.class.isAssignableFrom(loaded.getDomainObjectType())) {
loadedPrograms.add((Loaded<Program>) loaded);
}
else {
try {
loaded.close();
}
catch (Exception e) {
throw new IOException(e);
}
}
}
if (loadedPrograms.isEmpty()) {
throw new LoadException("Domain objects were loaded, but none were Programs");
}
return new LoadResults<>(loadedPrograms);
}
}
/**
* Gets this {@link Builder}'s source as a {@link ByteProvider}
* <p>
* NOTE: The returned {@link ByteProvider} should always be
* {@link ByteProvider#close() closed} by the caller. If this {@link Builder}'s source
* originated from a {@link ByteProvider}, the {@link ByteProvider#close()} will be a
* no-op.
*
* @return This {@link Builder}'s source as a {@link Byte Provider}
* @throws IOException if there was an IO-related problem
* @throws LoadException if there was no defined source
* @throws CancelledException if the operation was cancelled
*/
private ByteProvider getSourceAsProvider()
throws IOException, LoadException, CancelledException {
FileSystemService fsService = FileSystemService.getInstance();
ByteProvider p;
if (provider != null) {
p = new ByteProviderWrapper(provider, provider.getFSRL()); // wrap to prevent closing
}
else if (fsrl != null) {
p = fsService.getByteProvider(fsrl, true, monitor);
}
else if (file != null) {
p = fsService.getByteProvider(fsService.getLocalFSRL(file), true, monitor);
}
else if (bytes != null) {
p = new ByteArrayProvider(bytes);
}
else {
throw new LoadException("No source to import!");
}
return p;
}
/**
* Gets the {@link LoadSpec} from the given {@link ByteProvider}
*
* @param p The {@link ByteProvider}
* @return A {@link LoadSpec}
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws LoadException if a {@link LoadSpec} was not found
*/
private LoadSpec getLoadSpec(ByteProvider p)
throws LanguageNotFoundException, LoadException {
LoaderMap loaderMap = LoaderService.getSupportedLoadSpecs(p, loaderFilter);
LoadSpecChooser loadSpecChooser =
languageId != null ? new LcsHintLoadSpecChooser(languageId, compilerSpecId)
: (compilerSpecId != null ? new CsHintLoadSpecChooser(compilerSpecId)
: LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED);
LoadSpec loadSpec = loadSpecChooser.choose(loaderMap);
if (loadSpec == null) {
String name = Objects.requireNonNullElse(p.getName(), "???");
Msg.info(ProgramLoader.class, "No load spec found for import file: " + name);
throw new LoadException("No load spec found");
}
return loadSpec;
}
/**
* Gets the {@link Loader} {@link Option}s, with any loader arguments applied
*
* @param p The {@link ByteProvider}
* @param loadSpec The {@link LoadSpec}
* @return The {@link Loader} {@link Option}s, with any loader arguments applied
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws LoadException if the {@link Loader} had {@code null} options
*/
private List<Option> getLoaderOptions(ByteProvider p, LoadSpec loadSpec)
throws LanguageNotFoundException, LoadException {
List<Option> options = loadSpec.getLoader().getDefaultOptions(p, loadSpec, null, false);
if (options == null) {
throw new LoadException("Cannot load with null options");
}
if (loaderArgs == null) {
return options;
}
LanguageCompilerSpecPair languageCompilerSpecPair =
loadSpec.getLanguageCompilerSpec();
AddressFactory addrFactory = null; // Address type options not permitted if null
if (languageCompilerSpecPair != null) {
// It is assumed that if languageCompilerSpecPair exists, then language will be found
addrFactory = DefaultLanguageService.getLanguageService()
.getLanguage(languageCompilerSpecPair.languageID)
.getAddressFactory();
}
for (Pair<String, String> pair : loaderArgs) {
String arg = pair.first, val = pair.second;
boolean foundIt = false;
for (Option option : options) {
if (option.getArg() != null && arg.equalsIgnoreCase(option.getArg())) {
Object oldVal = option.getValue();
if (option.parseAndSetValueByType(val, addrFactory)) {
Msg.info(ProgramLoader.class, String.format(
"Successfully applied \"%s\" to \"%s\" (old: \"%s\", new: \"%s\")",
arg, option.getName(), oldVal, val));
}
else {
Msg.error(ProgramLoader.class, String.format(
"Failed to apply \"%s\" to \"%s\" (old: \"%s\", bad: \"%s\")", arg,
option.getName(), oldVal, val));
return null;
}
foundIt = true;
break;
}
}
if (!foundIt) {
Msg.warn(ProgramLoader.class, "Skipping unsupported " + arg + " argument");
}
}
return options;
}
}
}

View file

@ -15,54 +15,32 @@
*/ */
package ghidra.app.util.importer; package ghidra.app.util.importer;
import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import generic.stl.Pair;
import ghidra.app.util.opinion.Loader; import ghidra.app.util.opinion.Loader;
/**
* Filters on one specific loader
*
* @deprecated Use {@link ProgramLoader.Builder#loaders(Class)} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
public class SingleLoaderFilter implements Predicate<Loader> { public class SingleLoaderFilter implements Predicate<Loader> {
private final Class<? extends Loader> single; private final Class<? extends Loader> single;
private List<Pair<String, String>> loaderArgs;
/** /**
* Create a new single loader filter from the given loader class. * Create a new single loader filter from the given loader class.
* *
* @param single The loader class used for this filter. * @param single The loader class used for this filter.
* @deprecated Use {@link ProgramLoader.Builder#loaders(Class)} instead
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public SingleLoaderFilter(Class<? extends Loader> single) { public SingleLoaderFilter(Class<? extends Loader> single) {
this.single = single; this.single = single;
} }
/**
* Create a new single loader filter from the given loader class and loader command line
* argument list.
*
* @param single The loader class used for this filter.
* @param loaderArgs The loader arguments used for this filter. Could be null if there
* are not arguments.
*/
public SingleLoaderFilter(Class<? extends Loader> single,
List<Pair<String, String>> loaderArgs) {
this.single = single;
this.loaderArgs = loaderArgs;
}
/**
* Gets the loader arguments tied to the loader in this filter.
*
* @return The loader arguments tied to the loader in this filter. Could be null if there
* are no arguments.
*/
public List<Pair<String, String>> getLoaderArgs() {
return loaderArgs;
}
@Override @Override
public boolean test(Loader loader) { public boolean test(Loader loader) {
if (loader.getClass().equals(single)) { return loader.getClass().equals(single);
return true;
}
return false;
} }
} }

View file

@ -31,6 +31,7 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.*;
import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Library;
@ -49,23 +50,51 @@ import ghidra.util.task.TaskMonitor;
*/ */
public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader { public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader {
/**
* Option to attempt to fix up the {@link Loaded} {@link Program}'s external programs with
* libraries discovered in the project. This alone does not cause new library programs to be
* loaded.
*/
public static final String LINK_EXISTING_OPTION_NAME = "Link Existing Project Libraries"; public static final String LINK_EXISTING_OPTION_NAME = "Link Existing Project Libraries";
static final boolean LINK_EXISTING_OPTION_DEFAULT = true; static final boolean LINK_EXISTING_OPTION_DEFAULT = true;
/**
* Path of a {@link DomainFolder} to search for libraries
*/
public static final String LINK_SEARCH_FOLDER_OPTION_NAME = "Project Library Search Folder"; public static final String LINK_SEARCH_FOLDER_OPTION_NAME = "Project Library Search Folder";
static final String LINK_SEARCH_FOLDER_OPTION_DEFAULT = ""; static final String LINK_SEARCH_FOLDER_OPTION_DEFAULT = "";
/**
* Whether or not to search for libraries on disk (or in a {@link GFileSystem})
*/
public static final String LOAD_LIBRARY_OPTION_NAME = "Load Libraries From Disk"; public static final String LOAD_LIBRARY_OPTION_NAME = "Load Libraries From Disk";
static final boolean LOAD_LIBRARY_OPTION_DEFAULT = false; static final boolean LOAD_LIBRARY_OPTION_DEFAULT = false;
/**
* A dummy option used to produce a custom renderer to select library search paths.
*
* @see LibrarySearchPathDummyOption
*/
public static final String LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME = "Library Search Paths"; public static final String LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME = "Library Search Paths";
/**
* How many levels of libraries to load
*/
public static final String DEPTH_OPTION_NAME = "Recursive Library Load Depth"; public static final String DEPTH_OPTION_NAME = "Recursive Library Load Depth";
static final int DEPTH_OPTION_DEFAULT = 1; static final int DEPTH_OPTION_DEFAULT = 1;
/**
* Path of a {@link DomainFolder} to save libraries to. This location will also be used as
* a location to {@link #LINK_EXISTING_OPTION_NAME search for already-loaded libraries}.
*/
public static final String LIBRARY_DEST_FOLDER_OPTION_NAME = "Library Destination Folder"; public static final String LIBRARY_DEST_FOLDER_OPTION_NAME = "Library Destination Folder";
static final String LIBRARY_DEST_FOLDER_OPTION_DEFAULT = ""; static final String LIBRARY_DEST_FOLDER_OPTION_DEFAULT = "";
/**
* A hidden option used by the {@link ImporterPlugin}'s "Load Libraries" action to inform this
* {@link Loader} that the {@link Program} to import has already been saved to the project and
* is currently open, and that only libraries should be loaded.
*/
public static final String LOAD_ONLY_LIBRARIES_OPTION_NAME = "Only Load Libraries"; // hidden public static final String LOAD_ONLY_LIBRARIES_OPTION_NAME = "Only Load Libraries"; // hidden
static final boolean LOAD_ONLY_LIBRARIES_OPTION_DEFAULT = false; static final boolean LOAD_ONLY_LIBRARIES_OPTION_DEFAULT = false;
@ -85,6 +114,31 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
Program program, TaskMonitor monitor, MessageLog log) Program program, TaskMonitor monitor, MessageLog log)
throws CancelledException, IOException; throws CancelledException, IOException;
/**
* {@inheritDoc}
* <p>
* In addition to loading the given program bytes, this implementation will attempt to locate
* the libraries that the program links to from the
* {@link #LINK_SEARCH_FOLDER_OPTION_NAME project library search folder}, the
* {@link #LIBRARY_DEST_FOLDER_OPTION_NAME library destination folder} and the
* {@link #LOAD_LIBRARY_OPTION_NAME libraries found on disk}. All of these locations are
* controlled by loader options.
* <P>
* If the hidden {@link #LOAD_ONLY_LIBRARIES_OPTION_NAME} option is set and the given project
* is not {@code null}, it is assumed that a {@link DomainFile} exists at
* {@code projectFolderPath/loadedName}, is open, and the provider corresponds to its contents.
* If this is the case, the primary (first) {@link Loaded} {@link Program} in the returned list
* will NOT be affected by a {@link LoadResults#save(TaskMonitor)} operation. It will be the
* responsibility of the user to save this open program if desired.
*
* @return A {@link List} of one or more {@link Loaded} {@link Program}s (created but not
* saved). The first element in the {@link List} will the primary program, with the remaining
* elements being any newly loaded libraries.
* @throws LoadException if the load failed in an unexpected way. If the
* {@link #LOAD_ONLY_LIBRARIES_OPTION_NAME} option is set, this exception will be thrown if
* the {@link DomainFile} at {@code projectFolderPath/loadedName} does not correspond to an
* open {@link Program}.
*/
@Override @Override
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String loadedName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String loadedName,
Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
@ -101,21 +155,28 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (!shouldLoadOnlyLibraries(options)) { if (!shouldLoadOnlyLibraries(options)) {
program = doLoad(provider, loadedName, loadSpec, libraryNameList, options, consumer, program = doLoad(provider, loadedName, loadSpec, libraryNameList, options, consumer,
log, monitor); log, monitor);
loadedProgramList.add(new Loaded<>(program, loadedName, projectFolderPath)); loadedProgramList.add(
new Loaded<>(program, loadedName, project, projectFolderPath, consumer));
log.appendMsg("------------------------------------------------\n"); log.appendMsg("------------------------------------------------\n");
} }
else if (project != null) { else {
ProjectData projectData = project.getProjectData(); if (project == null) {
DomainFile domainFile = projectData.getFile(projectFolderPath + "/" + loadedName); throw new LoadException("Cannot load only libraries...project is null");
}
DomainFile domainFile =
project.getProjectData().getFile(projectFolderPath + "/" + loadedName);
if (domainFile == null) { if (domainFile == null) {
throw new LoadException( throw new LoadException(
"Cannot load only libraries for a non-existant program"); "Cannot load only libraries for a non-existant program");
} }
if (!Program.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
throw new LoadException("Cannot load only libraries for a non-program");
}
program = (Program) domainFile.getOpenedDomainObject(consumer); program = (Program) domainFile.getOpenedDomainObject(consumer);
if (program == null) { if (program == null) {
throw new LoadException("Failed to acquire a Program"); throw new LoadException("Failed to acquire an open Program");
} }
loadedProgramList.add(new Loaded<>(program, domainFile)); loadedProgramList.add(new LoadedOpen<>(program, domainFile, consumer));
libraryNameList.addAll(getLibraryNames(provider, program)); libraryNameList.addAll(getLibraryNames(provider, program));
} }
@ -128,7 +189,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
} }
finally { finally {
if (!success) { if (!success) {
release(loadedProgramList, consumer); loadedProgramList.forEach(Loaded::close);
} }
} }
} }
@ -169,32 +230,45 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List<DomainFolder> searchFolders = List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options, log); getLibrarySearchFolders(loadedPrograms, project, options, log);
List<LibrarySearchPath> searchPaths = getLibrarySearchPaths( Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
loadedPrograms.getFirst().getDomainObject(), loadSpec, options, log, monitor); List<LibrarySearchPath> searchPaths;
try {
searchPaths = getLibrarySearchPaths(firstProgram, loadSpec, options, log, monitor);
}
finally {
firstProgram.release(this);
}
List<Loaded<Program>> saveablePrograms = List<Loaded<Program>> saveablePrograms = loadedPrograms
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList(); .stream()
.filter(loaded -> loaded.check(Predicate.not(Program::isTemporary)))
.toList();
monitor.initialize(saveablePrograms.size()); monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) { for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.increment(); monitor.increment();
Program program = loadedProgram.getDomainObject(); Program program = loadedProgram.getDomainObject(this);
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 ||
(extLibNames.length == 1 && Library.UNKNOWN.equals(extLibNames[0]))) {
continue; // skip program if no libraries defined
}
monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try { try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, searchPaths, ExternalManager extManager = program.getExternalManager();
options, monitor, log); String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 ||
(extLibNames.length == 1 && Library.UNKNOWN.equals(extLibNames[0]))) {
continue; // skip program if no libraries defined
}
monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, searchPaths,
options, monitor, log);
}
finally {
program.endTransaction(id, true);
}
} }
finally { finally {
program.endTransaction(id, true); program.release(this);
} }
} }
} }
@ -408,16 +482,22 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/ */
protected List<DomainFolder> getLibrarySearchFolders(List<Loaded<Program>> loadedPrograms, protected List<DomainFolder> getLibrarySearchFolders(List<Loaded<Program>> loadedPrograms,
Project project, List<Option> options, MessageLog log) { Project project, List<Option> options, MessageLog log) {
List<DomainFolder> searchFolders = new ArrayList<>(); Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath(); try {
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options); List<DomainFolder> searchFolders = new ArrayList<>();
DomainFolder destSearchFolder = String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
getLibraryDestinationSearchFolder(project, destPath, options); String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder linkSearchFolder = getLinkSearchFolder(project, DomainFolder destSearchFolder =
loadedPrograms.getFirst().getDomainObject(), projectFolderPath, options, log); getLibraryDestinationSearchFolder(project, destPath, options);
Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add); DomainFolder linkSearchFolder =
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add); getLinkSearchFolder(project, firstProgram, projectFolderPath, options, log);
return searchFolders; Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add);
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add);
return searchFolders;
}
finally {
firstProgram.release(this);
}
} }
/** /**
@ -559,18 +639,18 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
} }
else if (isLoadLibraries(options) || shouldSearchAllPaths(program, options, log)) { else if (isLoadLibraries(options) || shouldSearchAllPaths(program, options, log)) {
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(library, provider, Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(library, provider,
customSearchPaths, libraryDestFolderPath, unprocessed, depth, project, customSearchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor); desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary == null) { if (loadedLibrary == null) {
loadedLibrary = loadLibraryFromSearchPaths(library, provider, searchPaths, loadedLibrary = loadLibraryFromSearchPaths(library, provider, project,
libraryDestFolderPath, unprocessed, depth, desiredLoadSpec, options, searchPaths, libraryDestFolderPath, unprocessed, depth, desiredLoadSpec,
log, consumer, monitor); options, log, consumer, monitor);
} }
if (loadedLibrary != null) { if (loadedLibrary != null) {
boolean discarding = !loadLibraries || unprocessedLibrary.discard(); boolean temporary = !loadLibraries || unprocessedLibrary.temporary();
loadedLibrary.setDiscard(discarding); loadedLibrary.apply(p -> p.setTemporary(temporary));
loadedPrograms.add(loadedLibrary); loadedPrograms.add(loadedLibrary);
log.appendMsg(discarding ? "Library not saved to project." log.appendMsg(temporary ? "Library not saved to project."
: "Saving library to: " + loadedLibrary); : "Saving library to: " + loadedLibrary);
} }
log.appendMsg("------------------------------------------------\n"); log.appendMsg("------------------------------------------------\n");
@ -581,7 +661,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
} }
finally { finally {
if (!success) { if (!success) {
release(loadedPrograms, consumer); loadedPrograms.forEach(Loaded::close);
} }
Stream.of(customSearchPaths, searchPaths) Stream.of(customSearchPaths, searchPaths)
.flatMap(Collection::stream) .flatMap(Collection::stream)
@ -600,6 +680,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* *
* @param library The library to load * @param library The library to load
* @param provider The {@link ByteProvider} of the program being loaded * @param provider The {@link ByteProvider} of the program being loaded
* @param project The {@link Project}. Could be null if there is no project.
* @param searchPaths A {@link List} of {@link LibrarySearchPath}s that will be searched * @param searchPaths A {@link List} of {@link LibrarySearchPath}s that will be searched
* @param libraryDestFolderPath The path of the project folder to load the libraries into. * @param libraryDestFolderPath The path of the project folder to load the libraries into.
* Could be null if the specified project is null or a destination folder path could not be * Could be null if the specified project is null or a destination folder path could not be
@ -617,7 +698,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException if the user cancelled the load * @throws CancelledException if the user cancelled the load
*/ */
private Loaded<Program> loadLibraryFromSearchPaths(String library, ByteProvider provider, private Loaded<Program> loadLibraryFromSearchPaths(String library, ByteProvider provider,
List<LibrarySearchPath> searchPaths, String libraryDestFolderPath, Project project, List<LibrarySearchPath> searchPaths, String libraryDestFolderPath,
Queue<UnprocessedLibrary> unprocessed, int depth, LoadSpec desiredLoadSpec, Queue<UnprocessedLibrary> unprocessed, int depth, LoadSpec desiredLoadSpec,
List<Option> options, MessageLog log, Object consumer, TaskMonitor monitor) List<Option> options, MessageLog log, Object consumer, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, IOException {
@ -662,7 +743,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
folderPath = joinPaths(folderPath, FilenameUtils.getFullPath(library)); folderPath = joinPaths(folderPath, FilenameUtils.getFullPath(library));
} }
} }
return new Loaded<Program>(libraryProgram, simpleLibraryName, folderPath); return new Loaded<Program>(libraryProgram, simpleLibraryName, project, folderPath,
consumer);
} }
} }
finally { finally {
@ -947,9 +1029,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param name The name of the library * @param name The name of the library
* @param depth The recursive load depth of the library (based on the original binary being * @param depth The recursive load depth of the library (based on the original binary being
* loaded) * loaded)
* @param discard True if the library should be discarded (not saved) after processing * @param temporary True if the library is temporary and should be discarded prior to returning
* from the load
*/ */
protected record UnprocessedLibrary(String name, int depth, boolean discard) {/**/} protected record UnprocessedLibrary(String name, int depth, boolean temporary) {/**/}
/** /**
* Creates a new {@link Queue} of {@link UnprocessedLibrary}s, initialized filled with the * Creates a new {@link Queue} of {@link UnprocessedLibrary}s, initialized filled with the

View file

@ -131,12 +131,14 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
throws CancelledException, IOException { throws CancelledException, IOException {
if (shouldPerformOrdinalLookup(options)) { if (shouldPerformOrdinalLookup(options)) {
List<Loaded<Program>> saveablePrograms = List<Loaded<Program>> saveablePrograms = loadedPrograms
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList(); .stream()
.filter(loaded -> loaded.check(Predicate.not(Program::isTemporary)))
.toList();
monitor.initialize(saveablePrograms.size()); monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) { for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.checkCancelled(); monitor.checkCancelled();
Program program = loadedProgram.getDomainObject(); Program program = loadedProgram.getDomainObject(this);
int id = program.startTransaction("Ordinal fixups"); int id = program.startTransaction("Ordinal fixups");
try { try {
applyLibrarySymbols(program, messageLog, monitor); applyLibrarySymbols(program, messageLog, monitor);
@ -144,6 +146,7 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
} }
finally { finally {
program.endTransaction(id, true); // More efficient to commit when program will be discarded program.endTransaction(id, true); // More efficient to commit when program will be discarded
program.release(this);
} }
} }
} }

View file

@ -63,11 +63,10 @@ public abstract class AbstractProgramLoader implements Loader {
* <p> * <p>
* Note that when the load completes, the returned {@link Loaded} {@link Program}s are not * Note that when the load completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}). * {@link Loaded#save(TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link Loaded#release(Object)} when they are no longer * {@link Program}s with {@link Loaded#close()} when they are no longer needed.
* needed.
* *
* @param provider The bytes to load. * @param provider The bytes to load.
* @param loadedName A suggested name for the primary {@link Loaded} {@link Program}. * @param loadedName A suggested name for the primary {@link Loaded} {@link Program}.
@ -135,21 +134,26 @@ public abstract class AbstractProgramLoader implements Loader {
try { try {
for (Loaded<Program> loadedProgram : loadedPrograms) { for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.checkCancelled(); monitor.checkCancelled();
Program program = loadedProgram.getDomainObject(); Program program = loadedProgram.getDomainObject(this);
applyProcessorLabels(options, program); try {
program.setEventsEnabled(true); applyProcessorLabels(options, program);
program.setEventsEnabled(true);
}
finally {
program.release(this);
}
} }
// Subclasses can perform custom post-load fix-ups // Subclasses can perform custom post-load fix-ups
postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog, monitor); postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog, monitor);
// Discard unneeded programs // Discard temporary programs
Iterator<Loaded<Program>> iter = loadedPrograms.iterator(); Iterator<Loaded<Program>> iter = loadedPrograms.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Loaded<Program> loaded = iter.next(); Loaded<Program> loaded = iter.next();
if (loaded.shouldDiscard()) { if (loaded.check(p -> p.isTemporary())) {
iter.remove(); iter.remove();
loaded.release(consumer); loaded.close();
} }
} }
@ -158,7 +162,7 @@ public abstract class AbstractProgramLoader implements Loader {
} }
finally { finally {
if (!success) { if (!success) {
release(loadedPrograms, consumer); loadedPrograms.forEach(Loaded::close);
} }
postLoadCleanup(success); postLoadCleanup(success);
} }
@ -463,18 +467,6 @@ public abstract class AbstractProgramLoader implements Loader {
return DefaultLanguageService.getLanguageService(); return DefaultLanguageService.getLanguageService();
} }
/**
* Releases the given consumer from each of the provided {@link Loaded loaded programs}
*
* @param loadedPrograms A list of {@link Loaded loaded programs} which are no longer being used
* @param consumer The consumer that was marking the {@link Program}s as being used
*/
protected final void release(List<Loaded<Program>> loadedPrograms, Object consumer) {
for (Loaded<Program> loadedProgram : loadedPrograms) {
loadedProgram.getDomainObject().release(consumer);
}
}
private void applyProcessorLabels(List<Option> options, Program program) { private void applyProcessorLabels(List<Option> options, Program program) {
int id = program.startTransaction("Finalize load"); int id = program.startTransaction("Finalize load");
try { try {

View file

@ -67,8 +67,8 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
Program program = createProgram(provider, programName, imageBaseAddr, getName(), language, Program program = createProgram(provider, programName, imageBaseAddr, getName(), language,
compilerSpec, consumer); compilerSpec, consumer);
List<Loaded<Program>> loadedList = List<Loaded<Program>> loadedList = List.of(
List.of(new Loaded<Program>(program, programName, programFolderPath)); new Loaded<Program>(program, programName, project, programFolderPath, consumer));
int transactionID = program.startTransaction("Loading"); int transactionID = program.startTransaction("Loading");
boolean success = false; boolean success = false;
@ -81,7 +81,7 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
finally { finally {
program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded
if (!success) { if (!success) {
release(loadedList, consumer); loadedList.forEach(Loaded::close);
} }
} }
} }

View file

@ -283,7 +283,7 @@ public class BinaryLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, baseAddr, getName(), importerLanguage, Program prog = createProgram(provider, programName, baseAddr, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList = List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath)); List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false; boolean success = false;
try { try {
@ -294,7 +294,7 @@ public class BinaryLoader extends AbstractProgramLoader {
} }
finally { finally {
if (!success) { if (!success) {
release(loadedList, consumer); loadedList.forEach(Loaded::close);
} }
} }
} }

View file

@ -160,11 +160,7 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
ProjectData projectData = project != null ? project.getProjectData() : null; ProjectData projectData = project != null ? project.getProjectData() : null;
try (ExternalSymbolResolver esr = new ExternalSymbolResolver(projectData, monitor)) { try (ExternalSymbolResolver esr = new ExternalSymbolResolver(projectData, monitor)) {
for (Loaded<Program> loadedProgram : loadedPrograms) { loadedPrograms.forEach(p -> esr.addProgramToFixup(p));
esr.addProgramToFixup(
loadedProgram.getProjectFolderPath() + loadedProgram.getName(),
loadedProgram.getDomainObject());
}
esr.fixUnresolvedExternalSymbols(); esr.fixUnresolvedExternalSymbols();
esr.logInfo(messageLog::appendMsg, true); esr.logInfo(messageLog::appendMsg, true);
} }

View file

@ -60,7 +60,7 @@ public class GdtLoader implements Loader {
DataTypeArchive dtArchive = DataTypeArchive dtArchive =
loadPackedProgramDatabase(provider, filename, consumer, monitor); loadPackedProgramDatabase(provider, filename, consumer, monitor);
return new LoadResults<>(dtArchive, filename, projectFolderPath); return new LoadResults<>(dtArchive, filename, project, projectFolderPath, consumer);
} }
private DataTypeArchive loadPackedProgramDatabase(ByteProvider provider, String programName, private DataTypeArchive loadPackedProgramDatabase(ByteProvider provider, String programName,

View file

@ -78,7 +78,7 @@ public class GzfLoader implements Loader {
throws IOException, CancelledException, VersionException { throws IOException, CancelledException, VersionException {
Program program = loadPackedProgramDatabase(provider, programName, consumer, monitor); Program program = loadPackedProgramDatabase(provider, programName, consumer, monitor);
return new LoadResults<>(program, programName, projectFolderPath); return new LoadResults<>(program, programName, project, projectFolderPath, consumer);
} }
private Program loadPackedProgramDatabase(ByteProvider provider, String programName, private Program loadPackedProgramDatabase(ByteProvider provider, String programName,

View file

@ -154,7 +154,7 @@ public class IntelHexLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, null, getName(), importerLanguage, Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList = List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath)); List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false; boolean success = false;
try { try {
loadInto(provider, loadSpec, options, log, prog, monitor); loadInto(provider, loadSpec, options, log, prog, monitor);
@ -164,7 +164,7 @@ public class IntelHexLoader extends AbstractProgramLoader {
} }
finally { finally {
if (!success) { if (!success) {
release(loadedList, consumer); loadedList.forEach(Loaded::close);
} }
} }
} }

View file

@ -21,7 +21,6 @@ import java.util.function.Predicate;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -33,7 +32,7 @@ import ghidra.util.task.TaskMonitor;
* *
* @param <T> The type of {@link DomainObject}s that were loaded * @param <T> The type of {@link DomainObject}s that were loaded
*/ */
public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>> { public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>, AutoCloseable {
private final List<Loaded<T>> loadedList; private final List<Loaded<T>> loadedList;
@ -41,6 +40,8 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>
* Creates a new {@link LoadResults} that contains the given non-empty {@link List} of * Creates a new {@link LoadResults} that contains the given non-empty {@link List} of
* {@link Loaded} {@link DomainObject}s. The first entry in the {@link List} is assumed to be * {@link Loaded} {@link DomainObject}s. The first entry in the {@link List} is assumed to be
* the {@link #getPrimary() primary} {@link Loaded} {@link DomainObject}. * the {@link #getPrimary() primary} {@link Loaded} {@link DomainObject}.
* <p>
* This object needs to be {@link #close() closed} when done with it.
* *
* @param loadedList A {@link List} of {@link Loaded} {@link DomainObject}s * @param loadedList A {@link List} of {@link Loaded} {@link DomainObject}s
* @throws IllegalArgumentException if the provided {@link List} is null or empty * @throws IllegalArgumentException if the provided {@link List} is null or empty
@ -60,32 +61,77 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>
* *
* @param domainObject The loaded {@link DomainObject} * @param domainObject The loaded {@link DomainObject}
* @param name The name of the loaded {@link DomainObject}. If a * @param name The name of the loaded {@link DomainObject}. If a
* {@link #save(Project, Object, MessageLog, TaskMonitor) save} occurs, this will attempted to * {@link #save(TaskMonitor) save} occurs, this will attempted to be used for the resulting
* be used for the resulting {@link DomainFile}'s name. * {@link DomainFile}'s name.
* @param project If not null, the project this will get saved to during a
* {@link #save(TaskMonitor)} operation
* @param projectFolderPath The project folder path this will get saved to during a * @param projectFolderPath The project folder path this will get saved to during a
* {@link #save(Project, Object, MessageLog, TaskMonitor) save} operation. If null or empty, * {@link #save(TaskMonitor) save} operation. If null or empty, the root project folder will
* the root project folder will be used. * be used.
* @param consumer A reference to the object "consuming" the returned this
* {@link LoadResults}, used to ensure the underlying {@link DomainObject}s are only closed
* when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a
* {@link DomainObject} in a {@link LoadResults} transfers responsibility of releasing the
* given {@link DomainObject} to this {@link LoadResults}'s {@link #close()} method.
*/ */
public LoadResults(T domainObject, String name, String projectFolderPath) { public LoadResults(T domainObject, String name, Project project, String projectFolderPath,
this(List.of(new Loaded<T>(domainObject, name, projectFolderPath))); Object consumer) {
this(List.of(new Loaded<T>(domainObject, name, project, projectFolderPath, consumer)));
} }
/** /**
* Gets the "primary" {@link Loaded} {@link DomainObject}, who's meaning is defined by each * Gets the "primary" {@link Loaded} {@link DomainObject}, whose meaning is defined by each
* {@link Loader} implementation * {@link Loader} implementation
* *
* @return The "primary" {@link Loaded} {@link DomainObject} * @return The "primary" {@link Loaded} {@link DomainObject}
*/ */
public Loaded<T> getPrimary() { public Loaded<T> getPrimary() {
return loadedList.get(0); return loadedList.getFirst();
} }
/** /**
* Gets the "primary" {@link DomainObject}, who's meaning is defined by each {@link Loader} * Gets the "non-primary" {@link Loaded} {@link DomainObject}s, whose meaning is defined by each
* implementation * {@link Loader} implementation
* *
* @return The "non-primary" {@link Loaded} {@link DomainObject}s
*/
public List<Loaded<T>> getNonPrimary() {
return loadedList.stream().skip(1).toList();
}
/**
* Gets the "primary" {@link DomainObject}, whose meaning is defined by each {@link Loader}
* implementation.
* <p>
* NOTE: It is the responsibility of the caller to properly
* {@link DomainObject#release(Object) release} it when done. This
* {@link DomainObject#release(Object)} does not replace the requirement to
* {@link #close()} the {@link LoadResults} object when done.
*
* @param consumer A new reference to the object "consuming" the returned {@link DomainObject},
* used to ensure the underlying {@link DomainObject} is only released when every consumer is
* done with it (see {@link DomainObject#release(Object)}). NOTE: This method adds the given
* consumer to the returned {@link DomainObject}, requiring an explicit
* {@link DomainObject#release(Object)} to be called on the return value (this entire
* {@link LoadResults} must also still be {@link #close() closed}).
* @return The "primary" {@link DomainObject} * @return The "primary" {@link DomainObject}
*/ */
public T getPrimaryDomainObject(Object consumer) {
return loadedList.getFirst().getDomainObject(consumer);
}
/**
* Gets the "primary" loaded {@link DomainObject}, whose meaning is defined by each
* {@link Loader} implementation. Unsafe resource management is used. Temporarily exists to
* provide backwards compatibility.
*
* @return The "primary" {@link DomainObject}
* @deprecated This class's internal {@link DomainObject}s are now cleaned up with the
* {@link #close()} method. If the primary {@link DomainObject} needs to be retrieved from
* this class, instead use {@link #getPrimaryDomainObject(Object)} and independently clean up
* the new reference with a separate call to {@link DomainObject#release(Object)}.
*/
@Deprecated(since = "11.5", forRemoval = true)
public T getPrimaryDomainObject() { public T getPrimaryDomainObject() {
return loadedList.get(0).getDomainObject(); return loadedList.get(0).getDomainObject();
} }
@ -101,65 +147,44 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>
} }
/** /**
* {@link Loaded#save(Project, MessageLog, TaskMonitor) Saves} each {@link Loaded} * {@link Loaded#save(TaskMonitor) Saves} each {@link Loaded} {@link DomainObject} to the given
* {@link DomainObject} to the given {@link Project}. * {@link Project}.
* <p>
* NOTE: If any fail to save, none will be saved (already saved {@link DomainFile}s will be
* cleaned up/deleted), and all {@link Loaded} {@link DomainObject}s will have been
* {@link #release(Object) released}.
* *
* @param project The {@link Project} to save to
* @param consumer the consumer
* @param messageLog The log
* @param monitor A cancelable task monitor * @param monitor A cancelable task monitor
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
* @throws IOException If there was a problem saving * @throws IOException If there was a problem saving. A thrown exception may result in only some
* @see Loaded#save(Project, MessageLog, TaskMonitor) * of the {@link Loaded} elements being saved. It is the responsibility of the caller to clean
* things up appropriately.
* @see Loaded#save(TaskMonitor)
*/ */
public void save(Project project, Object consumer, MessageLog messageLog, TaskMonitor monitor) public void save(TaskMonitor monitor) throws CancelledException, IOException {
throws CancelledException, IOException { for (Loaded<T> loaded : loadedList) {
boolean success = false; loaded.save(monitor);
try {
for (Loaded<T> loaded : loadedList) {
loaded.save(project, messageLog, monitor);
}
success = true;
}
finally {
if (!success) {
for (Loaded<T> loaded : this) {
try {
loaded.release(consumer);
loaded.deleteSavedDomainFile(consumer);
}
catch (IOException e1) {
Msg.error(getClass(), "Failed to delete: " + loaded);
}
}
}
} }
} }
/** /**
* Notify all of the {@link Loaded} {@link DomainObject}s that the specified consumer is no * Unsafely notifies all of the {@link Loaded} {@link DomainObject}s that the specified consumer
* longer using them. When the last consumer invokes this method, the {@link Loaded} * is no longer using them. Temporarily exists to provide backwards compatibility.
* {@link DomainObject}s will be closed and will become invalid.
* *
* @param consumer the consumer * @param consumer the consumer
* @deprecated Use {@link #close()} instead
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public void release(Object consumer) { public void release(Object consumer) {
loadedList.forEach(loaded -> loaded.release(consumer)); loadedList.forEach(loaded -> loaded.release(consumer));
} }
/** /**
* Notify the filtered {@link Loaded} {@link DomainObject}s that the specified consumer is no * Unsafely notifies the filtered {@link Loaded} {@link DomainObject}s that the specified
* longer using them. When the last consumer invokes this method, the filtered {@link Loaded} * consumer is no longer using them. Temporarily exists to provide backwards compatibility.
* {@link DomainObject}s will be closed and will become invalid.
* *
* @param consumer the consumer * @param consumer the consumer
* @param filter a filter to apply to the {@link Loaded} {@link DomainObject}s prior to the * @param filter a filter to apply to the {@link Loaded} {@link DomainObject}s prior to the
* release * release
* @deprecated Use {@link #close()} instead
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public void release(Object consumer, Predicate<? super Loaded<T>> filter) { public void release(Object consumer, Predicate<? super Loaded<T>> filter) {
loadedList.stream().filter(filter).forEach(loaded -> loaded.release(consumer)); loadedList.stream().filter(filter).forEach(loaded -> loaded.release(consumer));
} }
@ -170,7 +195,10 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>
* {@link DomainObject}s will be closed and will become invalid. * {@link DomainObject}s will be closed and will become invalid.
* *
* @param consumer the consumer * @param consumer the consumer
* @deprecated Use {@link #getNonPrimary()} and {@link Loaded#close()} on the {@link List}
* elements instead
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public void releaseNonPrimary(Object consumer) { public void releaseNonPrimary(Object consumer) {
for (int i = 0; i < loadedList.size(); i++) { for (int i = 0; i < loadedList.size(); i++) {
if (i > 0) { if (i > 0) {
@ -179,6 +207,18 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>
} }
} }
/**
* Closes this {@link LoadResults} and releases the reference on the object consuming it.
* <p>
* NOTE: Any {@link DomainObject}s obtained via {@link #getPrimaryDomainObject(Object)} must
* still be explicitly {@link DomainObject#release(Object) released} after calling this method,
* since they were obtained with their own consumers.
*/
@Override
public void close() {
loadedList.forEach(Loaded::close);
}
@Override @Override
public Iterator<Loaded<T>> iterator() { public Iterator<Loaded<T>> iterator() {
return loadedList.iterator(); return loadedList.iterator();

View file

@ -17,8 +17,9 @@ package ghidra.app.util.opinion;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Predicate;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.exception.*; import ghidra.util.exception.*;
@ -28,65 +29,116 @@ import ghidra.util.task.TaskMonitor;
* A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded * A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded
* {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path * {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path
* for the loaded {@link DomainObject}, should it get saved to a project. * for the loaded {@link DomainObject}, should it get saved to a project.
* <p>
* NOTE: If an object of this type is marked as {@link #setDiscard(boolean) discardable}, it should
* be {@link #release(Object) released} and not saved.
* *
* @param <T> The type of {@link DomainObject} that was loaded * @param <T> The type of {@link DomainObject} that was loaded
*/ */
public class Loaded<T extends DomainObject> { public class Loaded<T extends DomainObject> implements AutoCloseable {
private final T domainObject; protected final T domainObject;
private final String name; protected final String name;
private String projectFolderPath; protected Project project;
protected String projectFolderPath;
protected Object loadedConsumer;
private DomainFile domainFile; protected DomainFile domainFile;
private boolean ignoreSave;
private boolean discard;
/** /**
* Creates a new {@link Loaded} object * Creates a new {@link Loaded} object.
* <p>
* This object needs to be {@link #close() closed} when done with it.
* *
* @param domainObject The loaded {@link DomainObject} * @param domainObject The loaded {@link DomainObject}
* @param name The name of the loaded {@link DomainObject}. If a * @param name The name of the loaded {@link DomainObject}. If a {@link #save(TaskMonitor)}
* {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for * occurs, this will attempted to be used for the resulting {@link DomainFile}'s name.
* the resulting {@link DomainFile}'s name. * @param project If not null, the project this will get saved to during a
* {@link #save(TaskMonitor)} operation
* @param projectFolderPath The project folder path this will get saved to during a * @param projectFolderPath The project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root * {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be
* project folder will be used. * used.
* @param consumer A reference to the object "consuming" the returned {@link Loaded}
* {@link DomainObject}, used to ensure the underlying {@link DomainObject} is only closed
* when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a
* {@link DomainObject} in a {@link Loaded} transfers responsibility of releasing the
* given {@link DomainObject} to this {@link Loaded}'s {@link #close()} method.
*/ */
public Loaded(T domainObject, String name, String projectFolderPath) { public Loaded(T domainObject, String name, Project project, String projectFolderPath,
Object consumer) {
this.domainObject = domainObject; this.domainObject = domainObject;
this.name = name; this.name = name;
this.project = project;
this.loadedConsumer = consumer;
setProjectFolderPath(projectFolderPath); setProjectFolderPath(projectFolderPath);
} }
/** /**
* Creates a {@link Loaded} view on an existing {@link DomainFile}. This type of {@link Loaded} * Gets the loaded {@link DomainObject}.
* object cannot be saved. * <p>
* NOTE: The given It is the responsibility of the caller to properly
* {@link DomainObject#release(Object) release} it when done. This
* {@link DomainObject#release(Object)} does not replace the requirement to
* {@link #close()} the {@link Loaded} object when done.
* *
* @param domainObject The loaded {@link DomainObject} * @param consumer A new reference to the object "consuming" the returned {@link DomainObject},
* @param domainFile The {@link DomainFile} to be loaded * used to ensure the underlying {@link DomainObject} is only released when every consumer is
* done with it (see {@link DomainObject#release(Object)}). NOTE: This method adds the given
* consumer to the returned {@link DomainObject}, requiring an explicit
* {@link DomainObject#release(Object)} to be called on the return value (this
* {@link Loaded} must also still be {@link #close() closed}).
* @return The loaded {@link DomainObject}
*/ */
public Loaded(T domainObject, DomainFile domainFile) { public T getDomainObject(Object consumer) {
this(domainObject, domainFile.getName(), domainFile.getParent().getPathname()); domainObject.addConsumer(consumer);
this.domainFile = domainFile; return domainObject;
this.ignoreSave = true;
} }
/** /**
* Gets the loaded {@link DomainObject} * Gets the loaded {@link DomainObject} with unsafe resource management. Temporarily exists
* to provide backwards compatibility.
* *
* @return The loaded {@link DomainObject} * @return The loaded {@link DomainObject}
* @deprecated This class's internal {@link DomainObject} is now cleaned up with the
* {@link #close()} method. If the {@link DomainObject} needs to be retrieved from this
* class, instead use {@link #getDomainObject(Object)} and independently clean up the new
* reference with a separate call to {@link DomainObject#release(Object)}.
*/ */
@Deprecated(since = "11.5", forRemoval = true)
public T getDomainObject() { public T getDomainObject() {
return domainObject; return domainObject;
} }
/** /**
* Gets the name of the loaded {@link DomainObject}. If a * Gets the loaded {@link DomainObject}'s type
* {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for *
* the resulting {@link DomainFile}'s name. * @return the loaded {@link DomainObject}'s type
*/
public Class<? extends DomainObject> getDomainObjectType() {
return domainObject.getClass();
}
/**
* Safely applies the given operation to the loaded {@link DomainObject} without the need to
* worry about resource management
*
* @param operation The operation to apply to the loaded {@link DomainObject}
*/
public void apply(Consumer<T> operation) {
operation.accept(domainObject);
}
/**
* Safely tests the given predicate on the loaded {@link DomainObject} without the need to
* worry about resource management
*
* @param predicate The predicate to test
* @return The result of the test
*/
public boolean check(Predicate<T> predicate) {
return predicate.test(domainObject);
}
/**
* Gets the name of the loaded {@link DomainObject}. If a {@link #save(TaskMonitor)} occurs,
* this will attempted to be used for the resulting {@link DomainFile}'s name.
* *
* @return the name of the loaded {@link DomainObject} * @return the name of the loaded {@link DomainObject}
*/ */
@ -95,8 +147,18 @@ public class Loaded<T extends DomainObject> {
} }
/** /**
* Gets the project folder path this will get saved to during a * Gets the {@link Project} this will get saved to during a {@link #save(TaskMonitor)} operation
* {@link #save(Project, MessageLog, TaskMonitor)} operation. *
*@return The {@link Project} this will get saved to during a {@link #save(TaskMonitor)}
* operation (could be null)
*/
public Project getProject() {
return project;
}
/**
* Gets the project folder path this will get saved to during a {@link #save(TaskMonitor)}
* operation.
* <p> * <p>
* NOTE: The returned path will always end with a "/". * NOTE: The returned path will always end with a "/".
* *
@ -107,12 +169,12 @@ public class Loaded<T extends DomainObject> {
} }
/** /**
* Sets the project folder path this will get saved to during a * Sets the project folder path this will get saved to during a {@link #save(TaskMonitor)}
* {@link #save(Project, MessageLog, TaskMonitor)} operation. * operation.
* *
* @param projectFolderPath The project folder path this will get saved to during a * @param projectFolderPath The project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root * {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be
* project folder will be used. * used.
*/ */
public void setProjectFolderPath(String projectFolderPath) { public void setProjectFolderPath(String projectFolderPath) {
if (projectFolderPath == null || projectFolderPath.isBlank()) { if (projectFolderPath == null || projectFolderPath.isBlank()) {
@ -124,19 +186,6 @@ public class Loaded<T extends DomainObject> {
this.projectFolderPath = projectFolderPath; this.projectFolderPath = projectFolderPath;
} }
/**
* Notify the loaded {@link DomainObject} that the specified consumer is no longer using it.
* When the last consumer invokes this method, the loaded {@link DomainObject} will be closed
* and will become invalid.
*
* @param consumer the consumer
*/
public void release(Object consumer) {
if (!domainObject.isClosed() && domainObject.isUsedBy(consumer)) {
domainObject.release(consumer);
}
}
/** /**
* Saves the loaded {@link DomainObject} to the given {@link Project} at this object's * Saves the loaded {@link DomainObject} to the given {@link Project} at this object's
* project folder path, using this object's name. * project folder path, using this object's name.
@ -146,8 +195,6 @@ public class Loaded<T extends DomainObject> {
* Therefore, it should not be assumed that the returned {@link DomainFile} will have the same * Therefore, it should not be assumed that the returned {@link DomainFile} will have the same
* name as a call to {@link #getName()}. * name as a call to {@link #getName()}.
* *
* @param project The {@link Project} to save to
* @param messageLog The log
* @param monitor A cancelable task monitor * @param monitor A cancelable task monitor
* @return The {@link DomainFile} where the save happened * @return The {@link DomainFile} where the save happened
* @throws CancelledException if the operation was cancelled * @throws CancelledException if the operation was cancelled
@ -155,13 +202,9 @@ public class Loaded<T extends DomainObject> {
* @throws IOException If there was an IO-related error, an invalid name was specified, or it * @throws IOException If there was an IO-related error, an invalid name was specified, or it
* was already successfully saved and still exists * was already successfully saved and still exists
*/ */
public DomainFile save(Project project, MessageLog messageLog, TaskMonitor monitor) public DomainFile save(TaskMonitor monitor)
throws CancelledException, ClosedException, IOException { throws CancelledException, ClosedException, IOException {
if (ignoreSave) {
return domainFile;
}
if (domainObject.isClosed()) { if (domainObject.isClosed()) {
throw new ClosedException( throw new ClosedException(
"Cannot saved closed DomainObject: " + domainObject.getName()); "Cannot saved closed DomainObject: " + domainObject.getName());
@ -202,13 +245,13 @@ public class Loaded<T extends DomainObject> {
/** /**
* Gets the loaded {@link DomainObject}'s associated {@link DomainFile} that was * Gets the loaded {@link DomainObject}'s associated {@link DomainFile} that was
* {@link #save(Project, MessageLog, TaskMonitor) saved} * {@link #save(TaskMonitor) saved}
* *
* @return The loaded {@link DomainObject}'s associated saved {@link DomainFile}, or null if * @return The loaded {@link DomainObject}'s associated saved {@link DomainFile}, or null if
* was not saved * was not saved
* @throws FileNotFoundException If the loaded {@link DomainObject} was saved but the associated * @throws FileNotFoundException If the loaded {@link DomainObject} was saved but the associated
* {@link DomainFile} no longer exists * {@link DomainFile} no longer exists
* @see #save(Project, MessageLog, TaskMonitor) * @see #save(TaskMonitor)
*/ */
public DomainFile getSavedDomainFile() throws FileNotFoundException { public DomainFile getSavedDomainFile() throws FileNotFoundException {
if (domainFile != null && !domainFile.exists()) { if (domainFile != null && !domainFile.exists()) {
@ -218,42 +261,32 @@ public class Loaded<T extends DomainObject> {
} }
/** /**
* Checks to see if this {@link Loaded} {@link DomainObject} should be discarded (not saved) * Unsafely notifies the loaded {@link DomainObject} that the specified consumer is no longer
* * using it. Temporarily exists to provide backwards compatibility.
* @return True if this {@link Loaded} {@link DomainObject} should be discarded; otherwise,
* false
*/
public boolean shouldDiscard() {
return discard;
}
/**
* Sets whether or not this {@link Loaded} {@link DomainObject} should be discarded (not saved)
*
* @param discard True if this {@link Loaded} {@link DomainObject} should be discarded;
* otherwise, false
*/
public void setDiscard(boolean discard) {
this.discard = discard;
}
/**
* Deletes the loaded {@link DomainObject}'s associated {@link DomainFile} that was
* {@link #save(Project, MessageLog, TaskMonitor) saved}. This method has no effect if it was
* never saved.
* <p>
* NOTE: The loaded {@link DomainObject} must be {@link #release(Object) released} prior to
* calling this method.
* *
* @param consumer the consumer * @param consumer the consumer
* @throws IOException If there was an issue deleting the saved {@link DomainFile} * @deprecated Use {@link #close()} instead
* @see #save(Project, MessageLog, TaskMonitor)
*/ */
void deleteSavedDomainFile(Object consumer) throws IOException { @Deprecated(since = "11.5", forRemoval = true)
if (domainFile != null && domainFile.exists()) { public void release(Object consumer) {
domainFile.delete(); if (!domainObject.isClosed() && domainObject.isUsedBy(consumer)) {
domainFile = null; domainObject.release(consumer);
}
}
/**
* Closes this {@link Loaded} {@link DomainObject} and releases the reference on the object
* consuming it.
* <p>
* NOTE: Any {@link DomainObject}s obtained via {@link #getDomainObject(Object)} must still be
* explicitly {@link DomainObject#release(Object) released} after calling this method, since
* they were obtained with their own consumers.
*/
@Override
public void close() {
if (loadedConsumer != null && !domainObject.isClosed() &&
domainObject.isUsedBy(loadedConsumer)) {
domainObject.release(loadedConsumer);
} }
} }

View file

@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.opinion;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.util.task.TaskMonitor;
/**
* A loaded, open {@link DomainObject} that has already been saved to a {@link DomainFile}
*
* @param <T> The type of open {@link DomainObject}
*/
public class LoadedOpen<T extends DomainObject> extends Loaded<T> {
/**
* Creates a {@link Loaded} view on an existing {@link DomainFile}. This type of {@link Loaded}
* object cannot be re-saved.
*
* @param domainObject The loaded {@link DomainObject}
* @param domainFile The {@link DomainFile} associated with the loaded {@link DomainObject}
* @param consumer A reference to the object "consuming" the returned {@link Loaded}
* {@link DomainObject}, used to ensure the underlying {@link DomainObject} is only closed
* when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a
* {@link DomainObject} in a {@link Loaded} transfers responsibility of releasing the
* given {@link DomainObject} to this {@link Loaded}'s {@link #close()} method.
* @throws LoadException if the given {@link DomainFile} is not open
*/
public LoadedOpen(T domainObject, DomainFile domainFile, Object consumer) throws LoadException {
super(domainObject, domainFile.getName(), null, domainFile.getParent().getPathname(),
consumer);
this.domainFile = domainFile;
if (!domainFile.isOpen()) {
throw new LoadException(domainFile + " is not open");
}
}
@Override
public DomainFile save(TaskMonitor monitor) {
return domainFile;
}
}

View file

@ -83,11 +83,10 @@ public interface Loader extends ExtensionPoint, Comparable<Loader> {
* <p> * <p>
* Note that when the load completes, the returned {@link Loaded} {@link DomainObject}s are not * Note that when the load completes, the returned {@link Loaded} {@link DomainObject}s are not
* saved to a project. That is the responsibility of the caller (see * saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). * {@link LoadResults#save(boolean, TaskMonitor)}).
* <p> * <p>
* It is also the responsibility of the caller to release the returned {@link Loaded} * It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link DomainObject}s with {@link LoadResults#release(Object)} when they are no longer * {@link DomainObject}s with {@link LoadResults#close()} when they are no longer needed.
* needed.
* *
* @param provider The bytes to load. * @param provider The bytes to load.
* @param loadedName A suggested name for the primary {@link Loaded} {@link DomainObject}. * @param loadedName A suggested name for the primary {@link Loaded} {@link DomainObject}.
@ -105,7 +104,9 @@ public interface Loader extends ExtensionPoint, Comparable<Loader> {
* @param loadSpec The {@link LoadSpec} to use during load. * @param loadSpec The {@link LoadSpec} to use during load.
* @param options The load options. * @param options The load options.
* @param messageLog The message log. * @param messageLog The message log.
* @param consumer A consumer object for generated {@link DomainObject}s. * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
* to ensure the underlying {@link Program}s are only closed when every consumer is done
* with it (see {@link LoadResults#close()}).
* @param monitor A task monitor. * @param monitor A task monitor.
* @return The {@link LoadResults} which contains one or more {@link Loaded} * @return The {@link LoadResults} which contains one or more {@link Loaded}
* {@link DomainObject}s (created but not saved). * {@link DomainObject}s (created but not saved).

View file

@ -403,14 +403,20 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
List<DomainFolder> searchFolders = List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options, log); getLibrarySearchFolders(loadedPrograms, project, options, log);
List<LibrarySearchPath> searchPaths = getLibrarySearchPaths( Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
loadedPrograms.getFirst().getDomainObject(), loadSpec, options, log, monitor); List<LibrarySearchPath> searchPaths;
try {
searchPaths = getLibrarySearchPaths(firstProgram, loadSpec, options, log, monitor);
}
finally {
firstProgram.release(this);
}
monitor.initialize(loadedPrograms.size()); monitor.initialize(loadedPrograms.size());
for (Loaded<Program> loadedProgram : loadedPrograms) { for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.increment(); monitor.increment();
Program program = loadedProgram.getDomainObject(); Program program = loadedProgram.getDomainObject(this);
int id = program.startTransaction("Reexporting"); int id = program.startTransaction("Reexporting");
try { try {
reexport(program, loadedPrograms, searchFolders, searchPaths, options, monitor, reexport(program, loadedPrograms, searchFolders, searchPaths, options, monitor,
@ -421,6 +427,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
} }
finally { finally {
program.endTransaction(id, true); program.endTransaction(id, true);
program.release(this);
} }
} }
} }
@ -451,12 +458,11 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
for (String path : getReexportPaths(program, log)) { for (String path : getReexportPaths(program, log)) {
monitor.checkCancelled(); monitor.checkCancelled();
Program programToRelease = null; Program lib = null;
try { try {
Loaded<Program> match = findLibraryInLoadedList(loadedPrograms, path); Loaded<Program> match = findLibraryInLoadedList(loadedPrograms, path);
Program lib = null;
if (match != null) { if (match != null) {
lib = match.getDomainObject(); lib = match.getDomainObject(this);
} }
if (lib == null) { if (lib == null) {
for (DomainFolder searchFolder : searchFolders) { for (DomainFolder searchFolder : searchFolders) {
@ -466,7 +472,9 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
DomainObject obj = df.getDomainObject(this, true, true, monitor); DomainObject obj = df.getDomainObject(this, true, true, monitor);
if (obj instanceof Program p) { if (obj instanceof Program p) {
lib = p; lib = p;
programToRelease = p; }
else {
obj.release(this);
} }
break; break;
} }
@ -498,8 +506,8 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
} }
} }
finally { finally {
if (programToRelease != null) { if (lib != null) {
programToRelease.release(this); lib.release(this);
} }
} }
} }

View file

@ -172,7 +172,7 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, null, getName(), importerLanguage, Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList = List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath)); List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false; boolean success = false;
try { try {
loadInto(provider, loadSpec, options, log, prog, monitor); loadInto(provider, loadSpec, options, log, prog, monitor);
@ -182,7 +182,7 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
} }
finally { finally {
if (!success) { if (!success) {
release(loadedList, consumer); loadedList.forEach(Loaded::close);
} }
} }
} }

View file

@ -122,7 +122,7 @@ public class MzLoader extends AbstractLibrarySupportLoader {
@Override @Override
public int getTierPriority() { public int getTierPriority() {
return 60; // we are less priority than PE! Important for AutoImporter return 60; // we are less priority than PE! Important for ProgramLoader
} }
/** /**

View file

@ -200,7 +200,7 @@ public class XmlLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage, Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList = List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath)); List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false; boolean success = false;
try { try {
success = doImport(result.lastXmlMgr, options, log, prog, monitor, false); success = doImport(result.lastXmlMgr, options, log, prog, monitor, false);
@ -212,7 +212,7 @@ public class XmlLoader extends AbstractProgramLoader {
} }
finally { finally {
if (!success) { if (!success) {
release(loadedList, consumer); loadedList.forEach(Loaded::close);
} }
} }
} }

View file

@ -22,7 +22,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.client.*; import ghidra.framework.client.*;
@ -573,7 +573,7 @@ public class GhidraProject {
* @param program * @param program
* the program on which the command is to be applied. * the program on which the command is to be applied.
*/ */
public void execute(Command cmd, Program program) { public void execute(Command<Program> cmd, Program program) {
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program); AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program);
cmd.applyTo(program); cmd.applyTo(program);
mgr.initializeOptions(); mgr.initializeOptions();
@ -593,67 +593,196 @@ public class GhidraProject {
openPrograms.put(program, id); openPrograms.put(program, id);
} }
public Program importProgram(File file, Language language, /**
CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException, * Automatically imports the given {@link File} with the best matching {@link Loader} that
InvalidNameException, VersionException, IOException { * supports the given language and compiler specification.
MessageLog messageLog = new MessageLog(); * <p>
LoadResults<Program> loadResults = AutoImporter.importByLookingForLcs(file, project, null, * NOTE: It is the responsibility of the caller to release the returned {@link Program}
language, compilerSpec, this, messageLog, MONITOR); * with {@link Program#release(Object)} when it is no longer needed, with {@code this}
Program program = loadResults.getPrimaryDomainObject(); * {@link GhidraProject} instance as the consumer.
loadResults.releaseNonPrimary(this); *
initializeProgram(program, false); * @param file The {@link File} to import
return program; * @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @return The imported {@link Program}
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public Program importProgram(File file, Language language, CompilerSpec compilerSpec)
throws CancelledException, VersionException, LanguageNotFoundException, LoadException,
IOException {
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(project)
.language(language)
.compiler(compilerSpec)
.monitor(MONITOR)
.load()) {
Program program = loadResults.getPrimaryDomainObject(this);
initializeProgram(program, false);
return program;
}
} }
/**
* Automatically imports the given {@link File} with the best matching {@link Loader} that
* supports the given processor.
* <p>
* NOTE: It is the responsibility of the caller to release the returned {@link Program}
* with {@link Program#release(Object)} when it is no longer needed, with {@code this}
* {@link GhidraProject} instance as the consumer.
*
* @param file The {@link File} to import
* @param processor The desired {@link Processor}
* @return The imported {@link Program}
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public Program importProgram(File file, Processor processor) throws CancelledException, public Program importProgram(File file, Processor processor) throws CancelledException,
DuplicateNameException, InvalidNameException, VersionException, IOException { VersionException, LanguageNotFoundException, LoadException, IOException {
LanguageService svc = DefaultLanguageService.getLanguageService(); LanguageService svc = DefaultLanguageService.getLanguageService();
Language language = svc.getDefaultLanguage(processor); Language language = svc.getDefaultLanguage(processor);
CompilerSpec compilerSpec = language.getDefaultCompilerSpec(); CompilerSpec compilerSpec = language.getDefaultCompilerSpec();
return importProgram(file, language, compilerSpec); return importProgram(file, language, compilerSpec);
} }
/**
* Automatically imports the given {@link File} with the given {@link Loader}.
* <p>
* NOTE: It is the responsibility of the caller to release the returned {@link Program}
* with {@link Program#release(Object)} when it is no longer needed, with {@code this}
* {@link GhidraProject} instance as the consumer.
*
* @param file The {@link File} to import
* @param loaderClass The desired {@link Loader}
* @return The imported {@link Program}
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public Program importProgram(File file, Class<? extends Loader> loaderClass) public Program importProgram(File file, Class<? extends Loader> loaderClass)
throws CancelledException, DuplicateNameException, InvalidNameException, throws CancelledException, VersionException, LanguageNotFoundException, LoadException,
VersionException, IOException { IOException {
MessageLog messageLog = new MessageLog(); try (LoadResults<Program> loadResults = ProgramLoader.builder()
LoadResults<Program> loadResults = AutoImporter.importByUsingSpecificLoaderClass(file, .source(file)
project, null, loaderClass, null, this, messageLog, MONITOR); .project(project)
Program program = loadResults.getPrimaryDomainObject(); .loaders(loaderClass)
loadResults.releaseNonPrimary(this); .monitor(MONITOR)
initializeProgram(program, false); .load()) {
return program; Program program = loadResults.getPrimaryDomainObject(this);
initializeProgram(program, false);
return program;
}
} }
/**
* Automatically imports the given {@link File} with the given {@link Loader}, {@link Language},
* and {@link CompilerSpec compiler specification}.
* <p>
* NOTE: It is the responsibility of the caller to release the returned {@link Program}
* with {@link Program#release(Object)} when it is no longer needed, with {@code this}
* {@link GhidraProject} instance as the consumer.
*
* @param file The {@link File} to import
* @param loaderClass The desired {@link Loader}
* @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @return The imported {@link Program}
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public Program importProgram(File file, Class<? extends Loader> loaderClass, Language language, public Program importProgram(File file, Class<? extends Loader> loaderClass, Language language,
CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException, CompilerSpec compilerSpec) throws CancelledException, VersionException,
InvalidNameException, VersionException, IOException { LanguageNotFoundException, LoadException, IOException {
MessageLog messageLog = new MessageLog(); try (LoadResults<Program> loadResults = ProgramLoader.builder()
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, null); .source(file)
LcsHintLoadSpecChooser opinionChoose = new LcsHintLoadSpecChooser(language, compilerSpec); .project(project)
LoadResults<Program> loadResults = .loaders(loaderClass)
AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR, loaderFilter, .language(language)
opinionChoose, null, new LoaderArgsOptionChooser(loaderFilter)); .compiler(compilerSpec)
loadResults.releaseNonPrimary(this); .monitor(MONITOR)
return loadResults.getPrimaryDomainObject(); .load()) {
Program program = loadResults.getPrimaryDomainObject(this);
initializeProgram(program, false);
return program;
}
} }
public Program importProgram(File file) throws CancelledException, /**
DuplicateNameException, InvalidNameException, VersionException, IOException { * Automatically imports the given {@link File}.
MessageLog messageLog = new MessageLog(); * <p>
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(file, project, * NOTE: It is the responsibility of the caller to release the returned {@link Program}
null, this, messageLog, MONITOR); * with {@link Program#release(Object)} when it is no longer needed, with {@code this}
Program program = loadResults.getPrimaryDomainObject(); * {@link GhidraProject} instance as the consumer.
loadResults.releaseNonPrimary(this); *
initializeProgram(program, false); * @param file The {@link File} to import
return program; * @return The imported {@link Program}
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public Program importProgram(File file) throws CancelledException, VersionException,
LanguageNotFoundException, LoadException, IOException {
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(project)
.monitor(MONITOR)
.load()) {
Program program = loadResults.getPrimaryDomainObject(this);
initializeProgram(program, false);
return program;
}
} }
public Program importProgramFast(File file) throws CancelledException, DuplicateNameException, /**
InvalidNameException, VersionException, IOException { * Automatically imports the given {@link File}.
Program program = importByStealingCodeFromAutoImporterByUsingBestGuess(file); * <p>
initializeProgram(program, false); * NOTE: It is the responsibility of the caller to release the returned {@link Program}
return program; * with {@link Program#release(Object)} when it is no longer needed, with {@code this}
* {@link GhidraProject} instance as the consumer.
*
* @param file The {@link File} to import
* @return The imported {@link Program}
* @throws IOException if there was an IO-related problem loading
* @throws LanguageNotFoundException if there was a problem getting the language
* @throws CancelledException if the operation was cancelled
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if there was a problem loading
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public Program importProgramFast(File file) throws CancelledException, VersionException,
LanguageNotFoundException, LoadException, IOException {
return importProgram(file);
} }
//================================================================================================== //==================================================================================================
@ -673,21 +802,6 @@ public class GhidraProject {
} }
} }
private Program importByStealingCodeFromAutoImporterByUsingBestGuess(File file)
throws CancelledException, DuplicateNameException, InvalidNameException,
VersionException, IOException {
MessageLog messageLog = new MessageLog();
String programNameOverride = null;
LoadResults<Program> loadResults =
AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR,
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED,
programNameOverride, OptionChooser.DEFAULT_OPTIONS);
loadResults.releaseNonPrimary(this);
return loadResults.getPrimaryDomainObject();
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================

View file

@ -525,7 +525,7 @@ public class ImporterPlugin extends Plugin
Program program = manager.getCurrentProgram(); Program program = manager.getCurrentProgram();
TaskLauncher.launchModal("Show Load Libraries Dialog", monitor -> { TaskLauncher.launchModal("Show Load Libraries Dialog", monitor -> {
ImporterUtilities.showLoadLibrariesDialog(program, tool, manager, monitor); ImporterUtilities.showLoadLibrariesDialog(program, tool, monitor);
}); });
} }

View file

@ -265,8 +265,16 @@ public class ImporterUtilities {
} }
public static void showLoadLibrariesDialog(Program program, PluginTool tool, /**
ProgramManager manager, TaskMonitor monitor) { * Constructs a {@link LoadLibrariesOptionsDialog} and shows it in the swing thread. If the
* given {@link Program} does not support library loading, a warning message will be shown
* instead.
*
* @param program The {@link Program}
* @param tool The {@link PluginTool}
* @param monitor {@link TaskMonitor}
*/
public static void showLoadLibrariesDialog(Program program, PluginTool tool,TaskMonitor monitor) {
Objects.requireNonNull(monitor); Objects.requireNonNull(monitor);
@ -279,9 +287,25 @@ public class ImporterUtilities {
try { try {
ByteProvider provider = getProvider(program); ByteProvider provider = getProvider(program);
if (provider == null) {
Msg.showWarn(null, null, LoadLibrariesOptionsDialog.TITLE,
"Cannot Load Libraries. Program does not have file bytes associated with it.");
return;
}
LoadSpec loadSpec = getLoadSpec(provider, program); LoadSpec loadSpec = getLoadSpec(provider, program);
if (loadSpec == null || loadSpec.getLoader()
.getDefaultOptions(provider, loadSpec, null, false)
.stream()
.noneMatch(e -> e.getName()
.equals(
AbstractLibrarySupportLoader.LOAD_ONLY_LIBRARIES_OPTION_NAME))) {
Msg.showWarn(null, null, LoadLibrariesOptionsDialog.TITLE,
"Cannot Load Libraries. Program does not support library loading.");
return;
}
AddressFactory addressFactory = AddressFactory addressFactory =
loadSpec.getLanguageCompilerSpec().getLanguage().getAddressFactory(); loadSpec.getLanguageCompilerSpec().getLanguage().getAddressFactory();
SystemUtilities.runSwingLater(() -> { SystemUtilities.runSwingLater(() -> {
OptionsDialog dialog = new LoadLibrariesOptionsDialog(provider, program, tool, OptionsDialog dialog = new LoadLibrariesOptionsDialog(provider, program, tool,
loadSpec, () -> addressFactory); loadSpec, () -> addressFactory);
@ -345,20 +369,12 @@ public class ImporterUtilities {
Program program = Program program =
doFSImportHelper((GFileSystemProgramProvider) refdFile.fsRef.getFilesystem(), doFSImportHelper((GFileSystemProgramProvider) refdFile.fsRef.getFilesystem(),
gfile, destFolder, consumer, monitor); gfile, destFolder, consumer, monitor);
if (program != null) { if (program == null) {
LoadResults<? extends DomainObject> loadResults = new LoadResults<>(program, return;
program.getName(), destFolder.getPathname()); }
boolean success = false; try (LoadResults<? extends DomainObject> loadResults = new LoadResults<>(program,
try { program.getName(), tool.getProject(), destFolder.getPathname(), consumer)) {
doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer, doPostImportProcessing(tool, programManager, fsrl, loadResults, "", monitor);
"", monitor);
success = true;
}
finally {
if (!success) {
program.release(consumer);
}
}
} }
} }
catch (Exception e) { catch (Exception e) {
@ -420,14 +436,14 @@ public class ImporterUtilities {
Object consumer = new Object(); Object consumer = new Object();
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader() try (LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(bp, programName, tool.getProject(), destFolder.getPathname(), loadSpec, .load(bp, programName, tool.getProject(), destFolder.getPathname(), loadSpec,
options, messageLog, consumer, monitor); options, messageLog, consumer, monitor)) {
loadResults.save(monitor);
doPostImportProcessing(tool, programManager, fsrl, loadResults,
messageLog.toString(), monitor);
}
loadResults.save(tool.getProject(), consumer, messageLog, monitor);
doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer,
messageLog.toString(), monitor);
} }
catch (CancelledException e) { catch (CancelledException e) {
// no need to show a message // no need to show a message
@ -440,35 +456,39 @@ public class ImporterUtilities {
private static Set<DomainFile> doPostImportProcessing(PluginTool pluginTool, private static Set<DomainFile> doPostImportProcessing(PluginTool pluginTool,
ProgramManager programManager, FSRL fsrl, ProgramManager programManager, FSRL fsrl,
LoadResults<? extends DomainObject> loadResults, Object consumer, String importMessages, LoadResults<? extends DomainObject> loadResults, String importMessages,
TaskMonitor monitor) throws CancelledException { TaskMonitor monitor) throws CancelledException {
boolean firstProgram = true; boolean firstProgram = true;
Set<DomainFile> importedFilesSet = new HashSet<>(); Set<DomainFile> importedFilesSet = new HashSet<>();
for (Loaded<? extends DomainObject> loaded : loadResults) { for (Loaded<? extends DomainObject> loaded : loadResults) {
monitor.checkCancelled(); monitor.checkCancelled();
Object consumer = new Object();
if (loaded.getDomainObject() instanceof Program program) { DomainObject obj = loaded.getDomainObject(consumer);
if (programManager != null) { try {
int openState = firstProgram if (obj instanceof Program) {
? ProgramManager.OPEN_CURRENT if (programManager != null) {
: ProgramManager.OPEN_VISIBLE; int openState = firstProgram
programManager.openProgram(program, openState); ? ProgramManager.OPEN_CURRENT
: ProgramManager.OPEN_VISIBLE;
programManager.openProgram((Program) obj, openState);
}
importedFilesSet.add(obj.getDomainFile());
} }
importedFilesSet.add(program.getDomainFile()); if (firstProgram) {
} // currently we only show results for the imported program, not any libraries
if (firstProgram) { displayResults(pluginTool, obj, obj.getDomainFile(), importMessages);
// currently we only show results for the imported program, not any libraries
displayResults(pluginTool, loaded.getDomainObject(),
loaded.getDomainObject().getDomainFile(), importMessages);
// Optionally echo loader message log to application.log // Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && !importMessages.isEmpty()) { if (!Loader.loggingDisabled && !importMessages.isEmpty()) {
Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages); Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages);
}
} }
firstProgram = false;
}
finally {
obj.release(consumer);
} }
loaded.release(consumer);
firstProgram = false;
} }
selectFiles(importedFilesSet); selectFiles(importedFilesSet);

View file

@ -69,14 +69,14 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog {
protected void okCallback() { protected void okCallback() {
TaskLauncher.launchNonModal(TITLE, monitor -> { TaskLauncher.launchNonModal(TITLE, monitor -> {
super.okCallback(); super.okCallback();
try { Object consumer = new Object();
Object consumer = new Object(); MessageLog messageLog = new MessageLog();
MessageLog messageLog = new MessageLog(); try (LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(provider, program.getDomainFile().getName(), tool.getProject(), .load(provider, program.getDomainFile().getName(), tool.getProject(),
program.getDomainFile().getParent().getPathname(), loadSpec, program.getDomainFile().getParent().getPathname(), loadSpec,
getOptions(), messageLog, consumer, monitor); getOptions(), messageLog, consumer, monitor)) {
loadResults.save(tool.getProject(), consumer, messageLog, monitor);
loadResults.save(monitor);
// Display results // Display results
String importMessages = messageLog.toString(); String importMessages = messageLog.toString();
@ -90,8 +90,6 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog {
else { else {
Msg.showInfo(this, null, TITLE, "The program has no libraries."); Msg.showInfo(this, null, TITLE, "The program has no libraries.");
} }
loadResults.release(consumer);
} }
catch (CancelledException e) { catch (CancelledException e) {
// no need to show a message // no need to show a message

View file

@ -143,26 +143,22 @@ public class ImportBatchTask extends Task {
} }
Pair<DomainFolder, String> destInfo = getDestinationInfo(batchLoadConfig, destFolder); Pair<DomainFolder, String> destInfo = getDestinationInfo(batchLoadConfig, destFolder);
Object consumer = new Object();
try { try {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
Project project = AppInfo.getActiveProject(); Project project = AppInfo.getActiveProject();
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader() try (LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(byteProvider, fixupProjectFilename(destInfo.second), project, .load(byteProvider, fixupProjectFilename(destInfo.second), project,
destInfo.first.getPathname(), loadSpec, destInfo.first.getPathname(), loadSpec,
getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog, getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog,
consumer, monitor); this, monitor)) {
// TODO: accumulate batch results // TODO: accumulate batch results
if (loadResults != null) { if (loadResults != null) {
try { loadResults.save(monitor);
loadResults.save(project, consumer, messageLog, monitor);
processImportResults(loadResults, batchLoadConfig, monitor); processImportResults(loadResults, batchLoadConfig, monitor);
} }
finally {
loadResults.release(consumer);
}
} }
totalAppsImported++; totalAppsImported++;
Msg.info(this, "Imported " + destInfo.first + "/ " + destInfo.second + ", " + Msg.info(this, "Imported " + destInfo.first + "/ " + destInfo.second + ", " +
@ -197,14 +193,20 @@ public class ImportBatchTask extends Task {
BatchLoadConfig appInfo, TaskMonitor monitor) { BatchLoadConfig appInfo, TaskMonitor monitor) {
for (Loaded<? extends DomainObject> loaded : loadResults) { for (Loaded<? extends DomainObject> loaded : loadResults) {
DomainObject obj = loaded.getDomainObject(); DomainObject obj = loaded.getDomainObject(this);
if (obj instanceof Program program) { try {
if (programManager != null && totalObjsImported < MAX_PROGRAMS_TO_OPEN) { if (obj instanceof Program program) {
programManager.openProgram(program, if (programManager != null && totalObjsImported < MAX_PROGRAMS_TO_OPEN) {
totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT programManager.openProgram(program,
: ProgramManager.OPEN_VISIBLE); totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT
: ProgramManager.OPEN_VISIBLE);
}
} }
} }
finally {
obj.release(this);
}
totalObjsImported++; totalObjsImported++;
} }
} }

View file

@ -24,8 +24,8 @@ import ghidra.GhidraException;
import ghidra.GhidraTestApplicationLayout; import ghidra.GhidraTestApplicationLayout;
import ghidra.app.plugin.core.analysis.EmbeddedMediaAnalyzer; import ghidra.app.plugin.core.analysis.EmbeddedMediaAnalyzer;
import ghidra.app.util.bin.*; import ghidra.app.util.bin.*;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadException; import ghidra.app.util.opinion.LoadException;
import ghidra.app.util.opinion.LoadResults; import ghidra.app.util.opinion.LoadResults;
import ghidra.framework.*; import ghidra.framework.*;
@ -81,19 +81,22 @@ public class ProgramExaminer {
private ProgramExaminer(ByteProvider provider) throws GhidraException { private ProgramExaminer(ByteProvider provider) throws GhidraException {
initializeGhidra(); initializeGhidra();
messageLog = new MessageLog(); messageLog = new MessageLog();
LoadResults<Program> loadResults = null;
try { try {
try { try (LoadResults<Program> loadResults = ProgramLoader.builder()
loadResults = AutoImporter.importByUsingBestGuess(provider, null, null, this, .source(provider)
messageLog, TaskMonitor.DUMMY); .log(messageLog)
program = loadResults.getPrimaryDomainObject(); .load()) {
program = loadResults.getPrimaryDomainObject(this);
} }
catch (LoadException e) { catch (LoadException e) {
try { try {
program = AutoImporter try (LoadResults<Program> loadResults = ProgramLoader.builder()
.importAsBinary(provider, null, null, defaultLanguage, null, this, .source(provider)
messageLog, TaskMonitor.DUMMY) .language(defaultLanguage)
.getDomainObject(); .log(messageLog)
.load()) {
program = loadResults.getPrimaryDomainObject(this);
}
} }
catch (LoadException e1) { catch (LoadException e1) {
throw new GhidraException( throw new GhidraException(
@ -106,9 +109,6 @@ public class ProgramExaminer {
throw new GhidraException(e); throw new GhidraException(e);
} }
finally { finally {
if (loadResults != null) {
loadResults.releaseNonPrimary(this);
}
try { try {
provider.close(); provider.close();
} }

View file

@ -21,6 +21,7 @@ import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import db.Transaction; import db.Transaction;
import ghidra.app.util.opinion.Loaded;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
@ -68,40 +69,33 @@ public class ExternalSymbolResolver implements Closeable {
* is called. * is called.
* <p> * <p>
* The program should be fully persisted to the project if using this method, otherwise use * The program should be fully persisted to the project if using this method, otherwise use
* {@link #addProgramToFixup(String, Program)} and specify the pathname the program will * {@link #addProgramToFixup(Loaded)}.
* be saved to.
* *
* @param program {@link Program} to fix * @param program {@link Program} to fix
*/ */
public void addProgramToFixup(Program program) { public void addProgramToFixup(Program program) {
addProgramToFixup(program.getDomainFile().getPathname(), program); String programPath = program.getDomainFile().getPathname();
}
/**
* Queues a program into this session that will be fixed when {@link #fixUnresolvedExternalSymbols()}
* is called.
*
* @param programPath string project path to the program
* @param program {@link Program} to fix
*/
public void addProgramToFixup(String programPath, Program program) {
programsToFix.add(new ProgramSymbolResolver(program, programPath)); programsToFix.add(new ProgramSymbolResolver(program, programPath));
addLoadedProgram(programPath, program);
}
/**
* Adds an already opened program to this session, allowing it to be used as an external
* library without needing to look it up in the current project.
*
* @param programPath project path to already opened program
* @param program {@link Program}
*/
public void addLoadedProgram(String programPath, Program program) {
if (loadedPrograms.put(programPath, program) == null) { if (loadedPrograms.put(programPath, program) == null) {
program.addConsumer(this); program.addConsumer(this);
} }
} }
/**
* Queues a {@link Loaded} {@link Program} into this session that will be fixed when
* {@link #fixUnresolvedExternalSymbols()} is called.
*
* @param loaded The {@link Loaded} {@link Program} to fix
*/
public void addProgramToFixup(Loaded<Program> loaded) {
Program program = loaded.getDomainObject(this);
String programPath = loaded.getProjectFolderPath() + loaded.getName();
programsToFix.add(new ProgramSymbolResolver(program, programPath));
if (loadedPrograms.put(programPath, program) != null) {
program.release(this);
}
}
/** /**
* Returns true if there was an error encountered when trying to open an external library. * Returns true if there was an error encountered when trying to open an external library.
* *
@ -122,7 +116,7 @@ public class ExternalSymbolResolver implements Closeable {
/** /**
* Resolves any unresolved external symbols in each program that has been queued up via * Resolves any unresolved external symbols in each program that has been queued up via
* {@link #addProgramToFixup(String, Program)}. * {@link #addProgramToFixup(Loaded)} or {@link #addProgramToFixup(Program)}.
* *
* @throws CancelledException if cancelled * @throws CancelledException if cancelled
*/ */
@ -152,7 +146,7 @@ public class ExternalSymbolResolver implements Closeable {
* released during {@link #close()} of this ExternalSymbolServer instance. * released during {@link #close()} of this ExternalSymbolServer instance.
* <p> * <p>
* This cache is shared between all ProgramSymbolResolver instances (that were created * This cache is shared between all ProgramSymbolResolver instances (that were created
* by calling {@link #addProgramToFixup(String, Program)}). * by calling {@link #addProgramToFixup(Loaded)} or {@link #addProgramToFixup(Program)}).
* *
* @param libPath project path to a library program * @param libPath project path to a library program
* @return {@link Program}, or null if not found or other error during opening * @return {@link Program}, or null if not found or other error during opening

View file

@ -103,13 +103,13 @@ public class ApkLoader extends DexLoader {
} }
finally { finally {
if (!success) { if (!success) {
release(allLoadedPrograms, consumer); allLoadedPrograms.forEach(Loaded::close);
} }
} }
if (allLoadedPrograms.isEmpty()) { if (allLoadedPrograms.isEmpty()) {
throw new LoadException("Operation finished with no programs to load"); throw new LoadException("Operation finished with no programs to load");
} }
link(allLoadedPrograms.stream().map(e -> e.getDomainObject()).toList(), log, monitor); link(allLoadedPrograms, log, monitor);
return allLoadedPrograms; return allLoadedPrograms;
} }
@ -300,11 +300,9 @@ public class ApkLoader extends DexLoader {
* @param log the message log * @param log the message log
* @param monitor the task monitor * @param monitor the task monitor
*/ */
private void link(List<Program> programList, MessageLog log, TaskMonitor monitor) { private void link(List<Loaded<Program>> programList, MessageLog log, TaskMonitor monitor) {
MultiDexLinker linker = new MultiDexLinker(programList); try (MultiDexLinker linker = new MultiDexLinker(programList)) {
try {
linker.link(monitor); linker.link(monitor);
linker.clear(monitor);
} }
catch (Exception e) { catch (Exception e) {
log.appendException(e); log.appendException(e);

View file

@ -16,33 +16,20 @@
package ghidra.file.formats.android.multidex; package ghidra.file.formats.android.multidex;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import ghidra.app.util.opinion.Loaded;
import ghidra.file.formats.android.dex.DexHeaderFactory; import ghidra.file.formats.android.dex.DexHeaderFactory;
import ghidra.file.formats.android.dex.format.DexHeader; import ghidra.file.formats.android.dex.format.DexHeader;
import ghidra.file.formats.android.dex.format.MethodIDItem; import ghidra.file.formats.android.dex.format.MethodIDItem;
import ghidra.file.formats.android.dex.util.DexUtil; import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.*;
import ghidra.program.model.listing.DataIterator;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.ExternalLocation; import ghidra.program.model.symbol.*;
import ghidra.program.model.symbol.ExternalManager; import ghidra.util.exception.*;
import ghidra.program.model.symbol.ExternalReference;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceManager;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
@ -50,7 +37,7 @@ import ghidra.util.task.TaskMonitor;
* via the "method_lookup" section using * via the "method_lookup" section using
* external references. * external references.
*/ */
public final class MultiDexLinker { public final class MultiDexLinker implements AutoCloseable {
private List<Program> programs; private List<Program> programs;
private Map<Program, DexHeader> dexMap = new HashMap<>(); private Map<Program, DexHeader> dexMap = new HashMap<>();
@ -109,8 +96,11 @@ public final class MultiDexLinker {
} }
} }
public MultiDexLinker(List<Program> programs) { public MultiDexLinker(List<Loaded<Program>> loadedPrograms) {
this.programs = new ArrayList<>(programs);//create copy of list this.programs = new ArrayList<>();
for (Loaded<Program> loaded : loadedPrograms) {
this.programs.add(loaded.getDomainObject(this));
}
} }
public void link(TaskMonitor monitor) throws CancelledException, IOException, public void link(TaskMonitor monitor) throws CancelledException, IOException,
@ -121,12 +111,12 @@ public final class MultiDexLinker {
linkPrograms(monitor); linkPrograms(monitor);
} }
public void clear(TaskMonitor monitor) throws CancelledException { @Override
Objects.requireNonNull(monitor); public void close() {
programs.forEach(p -> p.release(this));
programs.clear(); programs.clear();
dexMap.clear(); dexMap.clear();
for (DexHeader header : cmpMap.keySet()) { for (DexHeader header : cmpMap.keySet()) {
monitor.checkCancelled();
cmpMap.get(header).clear(); cmpMap.get(header).clear();
} }
cmpMap.clear(); cmpMap.clear();

View file

@ -19,17 +19,16 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.function.Predicate;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.*; import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.coff.*; import ghidra.app.util.bin.format.coff.*;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.lang.LanguageDescription; import ghidra.program.model.lang.LanguageDescription;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -39,8 +38,6 @@ import ghidra.util.task.CancelOnlyWrappingTaskMonitor;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class ImportMSLibs extends GhidraScript { public class ImportMSLibs extends GhidraScript {
final static Predicate<Loader> LOADER_FILTER = new SingleLoaderFilter(MSCoffLoader.class);
final static LoadSpecChooser LOADSPEC_CHOOSER = new CsHintLoadSpecChooser("windows");
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
@ -103,32 +100,32 @@ public class ImportMSLibs extends GhidraScript {
if (CoffMachineType.isMachineTypeDefined(header.getMagic())) { if (CoffMachineType.isMachineTypeDefined(header.getMagic())) {
String[] splits = splitPreferredName(preferredName); String[] splits = splitPreferredName(preferredName);
LoadResults<? extends DomainObject> loadResults = try (LoadResults<Program> loadResults =
AutoImporter.importFresh( ProgramLoader.builder()
coffProvider, .source(coffProvider)
state.getProject(), .project(state.getProject())
root.getPathname(), .projectFolderPath(root.getPathname())
this, .loaders(MSCoffLoader.class)
log, .compiler("windows")
new CancelOnlyWrappingTaskMonitor(monitor), .name(
LOADER_FILTER, mangleNameBecauseDomainFoldersAreSoRetro(
LOADSPEC_CHOOSER, splits[splits.length - 1]))
mangleNameBecauseDomainFoldersAreSoRetro(splits[splits.length - 1]), .log(log)
OptionChooser.DEFAULT_OPTIONS); .monitor(new CancelOnlyWrappingTaskMonitor(monitor))
.load()) {
try { for (Loaded<Program> loaded : loadResults) {
for (Loaded<? extends DomainObject> loaded : loadResults) { Program program = loaded.getDomainObject(this);
if (loaded.getDomainObject() instanceof Program program) { try {
loaded.save(state.getProject(), log, monitor); loaded.save(monitor);
DomainFolder destination = DomainFolder destination =
establishFolder(root, file, program, isDebug, splits); establishFolder(root, file, program, isDebug, splits);
program.getDomainFile().moveTo(destination); program.getDomainFile().moveTo(destination);
} }
finally {
program.release(this);
}
} }
} }
finally {
loadResults.release(this);
}
} }
} }
catch (LoadException e) { catch (LoadException e) {

View file

@ -53,7 +53,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.util.*; import java.util.*;
import java.util.function.Predicate;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@ -63,19 +62,19 @@ import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.coff.*; import ghidra.app.util.bin.format.coff.*;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.exception.*; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
public class MSLibBatchImportWorker extends GhidraScript { public class MSLibBatchImportWorker extends GhidraScript {
final static Predicate<Loader> LOADER_FILTER = new SingleLoaderFilter(MSCoffLoader.class);
final static LoadSpecChooser LOADSPEC_CHOOSER = new CsHintLoadSpecChooser("windows");
private static String getProcessId(String fallback) { private static String getProcessId(String fallback) {
// something like '<pid>@<hostname>', at least in SUN / Oracle JVMs // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
@ -173,8 +172,7 @@ public class MSLibBatchImportWorker extends GhidraScript {
} }
private void importLibrary(DomainFolder currentLibraryFolder, File file, MessageLog log) private void importLibrary(DomainFolder currentLibraryFolder, File file, MessageLog log)
throws CancelledException, DuplicateNameException, InvalidNameException, throws CancelledException, InvalidNameException, VersionException, IOException {
VersionException, IOException {
try (RandomAccessByteProvider provider = new RandomAccessByteProvider(file)) { try (RandomAccessByteProvider provider = new RandomAccessByteProvider(file)) {
if (!CoffArchiveHeader.isMatch(provider)) { if (!CoffArchiveHeader.isMatch(provider)) {
return; return;
@ -196,30 +194,34 @@ public class MSLibBatchImportWorker extends GhidraScript {
Pair<DomainFolder, String> pair = Pair<DomainFolder, String> pair =
getFolderAndUniqueFile(currentLibraryFolder, preferredName); getFolderAndUniqueFile(currentLibraryFolder, preferredName);
LoadResults<? extends DomainObject> loadResults = null; try (LoadResults<? extends DomainObject> loadResults =
try { ProgramLoader.builder()
loadResults = AutoImporter.importFresh(coffProvider, .source(coffProvider)
state.getProject(), pair.first.getPathname(), this, log, .project(state.getProject())
monitor, LOADER_FILTER, LOADSPEC_CHOOSER, pair.second, .projectFolderPath(pair.first.getPathname())
OptionChooser.DEFAULT_OPTIONS); .loaders(MSCoffLoader.class)
.compiler("windows")
.name(pair.second)
.log(log)
.monitor(monitor)
.load()) {
for (Loaded<? extends DomainObject> loaded : loadResults) { for (Loaded<? extends DomainObject> loaded : loadResults) {
if (loaded.getDomainObject() instanceof Program program) { DomainObject obj = loaded.getDomainObject(this);
loaded.save(state.getProject(), log, monitor); try {
println( if (obj instanceof Program) {
"Imported " + program.getDomainFile().getPathname()); loaded.save(monitor);
DomainFile progFile = program.getDomainFile(); DomainFile progFile = obj.getDomainFile();
println("Imported " + progFile.getPathname());
if (!progFile.isVersioned()) { if (!progFile.isVersioned()) {
progFile.addToVersionControl(initalCheckInComment, progFile.addToVersionControl(initalCheckInComment,
false, monitor); false, monitor);
}
} }
} }
} finally {
} obj.release(this);
finally { }
if (loadResults != null) {
loadResults.release(this);
} }
} }
} }

View file

@ -17,7 +17,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.function.Predicate;
import generic.stl.Pair; import generic.stl.Pair;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
@ -25,8 +24,10 @@ import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.coff.*; import ghidra.app.util.bin.format.coff.*;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadResults;
import ghidra.app.util.opinion.MSCoffLoader;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
@ -35,8 +36,6 @@ import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class RecursiveRecursiveMSLibImport extends GhidraScript { public class RecursiveRecursiveMSLibImport extends GhidraScript {
final static Predicate<Loader> LOADER_FILTER = new SingleLoaderFilter(MSCoffLoader.class);
final static LoadSpecChooser LOADSPEC_CHOOSER = new CsHintLoadSpecChooser("windows");
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
@ -152,18 +151,19 @@ public class RecursiveRecursiveMSLibImport extends GhidraScript {
Pair<DomainFolder, String> pair = Pair<DomainFolder, String> pair =
establishProgramFolder(currentLibrary, preferredName); establishProgramFolder(currentLibrary, preferredName);
LoadResults<? extends DomainObject> loadResults = try (LoadResults<? extends DomainObject> loadResults =
AutoImporter.importFresh(coffProvider, state.getProject(), ProgramLoader.builder()
pair.first.getPathname(), this, log, monitor, LOADER_FILTER, .source(coffProvider)
LOADSPEC_CHOOSER, .project(state.getProject())
mangleNameBecauseDomainFoldersAreSoRetro(pair.second), .projectFolderPath(pair.first.getPathname())
OptionChooser.DEFAULT_OPTIONS); .loaders(MSCoffLoader.class)
.compiler("windows")
try { .name(
loadResults.save(state.getProject(), this, log, monitor); mangleNameBecauseDomainFoldersAreSoRetro(pair.second))
} .log(log)
finally { .monitor(monitor)
loadResults.release(this); .load()) {
loadResults.save(monitor);
} }
} }
} }

View file

@ -194,7 +194,7 @@ public class SarifLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage, Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList = List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath)); List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false; boolean success = false;
try { try {
success = doImport(result.lastSarifMgr, options, log, prog, monitor, false); success = doImport(result.lastSarifMgr, options, log, prog, monitor, false);
@ -206,7 +206,7 @@ public class SarifLoader extends AbstractProgramLoader {
} }
finally { finally {
if (!success) { if (!success) {
release(loadedList, consumer); loadedList.forEach(Loaded::close);
} }
} }
} }

View file

@ -20,8 +20,7 @@ import java.io.File;
import org.junit.*; import org.junit.*;
import db.Transaction; import db.Transaction;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadResults; import ghidra.app.util.opinion.LoadResults;
import ghidra.base.project.GhidraProject; import ghidra.base.project.GhidraProject;
import ghidra.framework.Application; import ghidra.framework.Application;
@ -57,15 +56,19 @@ public class TutorialDebuggerMaintenance extends AbstractGhidraHeadedIntegration
@Test @Test
public void testRecreateTermminesGzf() throws Throwable { public void testRecreateTermminesGzf() throws Throwable {
File termmines = Application.getModuleDataFile("TestResources", "termmines").getFile(false); File termmines = Application.getModuleDataFile("TestResources", "termmines").getFile(false);
LoadResults<Program> results = AutoImporter.importByUsingBestGuess(termmines, try (LoadResults<Program> loadResults = ProgramLoader.builder()
env.getProject(), "/", this, new MessageLog(), CONSOLE); .source(termmines)
program = results.getPrimaryDomainObject(); .project(env.getProject())
try (Transaction tx = program.openTransaction("Analyze")) { .monitor(CONSOLE)
program.setExecutablePath("/tmp/termmines"); .load()) {
GhidraProject.analyze(program); program = loadResults.getPrimaryDomainObject(this);
try (Transaction tx = program.openTransaction("Analyze")) {
program.setExecutablePath("/tmp/termmines");
GhidraProject.analyze(program);
}
File dest = new File(termmines.getParentFile(), "termmines.gzf");
dest.delete();
program.saveToPackedFile(dest, CONSOLE);
} }
File dest = new File(termmines.getParentFile(), "termmines.gzf");
dest.delete();
program.saveToPackedFile(dest, CONSOLE);
} }
} }

View file

@ -15,7 +15,7 @@
*/ */
package ghidraclass.debugger.screenshot; package ghidraclass.debugger.screenshot;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.*;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@ -74,8 +74,7 @@ import ghidra.app.plugin.core.terminal.TerminalProvider;
import ghidra.app.script.GhidraState; import ghidra.app.script.GhidraState;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadResults; import ghidra.app.util.opinion.LoadResults;
import ghidra.async.AsyncTestUtils; import ghidra.async.AsyncTestUtils;
import ghidra.debug.api.modules.ModuleMapProposal; import ghidra.debug.api.modules.ModuleMapProposal;
@ -389,13 +388,15 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator
protected Program importModule(TraceModule module) throws Throwable { protected Program importModule(TraceModule module) throws Throwable {
Program prog = null; Program prog = null;
try { long snap = flatDbg.getCurrentSnap();
long snap = flatDbg.getCurrentSnap(); try (LoadResults<Program> result = ProgramLoader.builder()
MessageLog log = new MessageLog(); .source(new File(module.getName(snap)))
LoadResults<Program> result = AutoImporter.importByUsingBestGuess( .project(env.getProject())
new File(module.getName(snap)), env.getProject(), "/", this, log, monitor); .monitor(monitor)
result.save(env.getProject(), this, log, monitor); .load()) {
prog = result.getPrimaryDomainObject();
result.save(monitor);
prog = result.getPrimaryDomainObject(this);
GhidraProgramUtilities.markProgramNotToAskToAnalyze(prog); GhidraProgramUtilities.markProgramNotToAskToAnalyze(prog);
programManager.openProgram(prog); programManager.openProgram(prog);
} }

View file

@ -15,9 +15,8 @@
*/ */
package ghidra.app.plugin.core.debug.gui.tracermi.launcher; package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertFalse; import static org.junit.Assume.*;
import static org.junit.Assume.assumeTrue;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.*;
@ -31,8 +30,7 @@ import ghidra.app.plugin.core.analysis.AnalysisBackgroundCommand;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.services.TraceRmiLauncherService; import ghidra.app.services.TraceRmiLauncherService;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadResults; import ghidra.app.util.opinion.LoadResults;
import ghidra.debug.api.ValStr; import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
@ -80,9 +78,13 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
@Test @Test
public void testGetClassName() throws Exception { public void testGetClassName() throws Exception {
ResourceFile rf = Application.getModuleDataFile("TestResources", "HelloWorld.class"); ResourceFile rf = Application.getModuleDataFile("TestResources", "HelloWorld.class");
LoadResults<Program> results = AutoImporter.importByUsingBestGuess(rf.getFile(false), try (LoadResults<Program> results = ProgramLoader.builder()
env.getProject(), "/", this, new MessageLog(), monitor); .source(rf.getFile(false))
program = results.getPrimaryDomainObject(); .project(env.getProject())
.monitor(monitor)
.load()) {
program = results.getPrimaryDomainObject(this);
}
AutoAnalysisManager analyzer = AutoAnalysisManager.getAnalysisManager(program); AutoAnalysisManager analyzer = AutoAnalysisManager.getAnalysisManager(program);
analyzer.reAnalyzeAll(null); analyzer.reAnalyzeAll(null);
Command<Program> cmd = new AnalysisBackgroundCommand(analyzer, false); Command<Program> cmd = new AnalysisBackgroundCommand(analyzer, false);

View file

@ -30,17 +30,16 @@ import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Palette; import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.references.*; import ghidra.app.plugin.core.references.*;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadResults; import ghidra.app.util.opinion.LoadResults;
import ghidra.app.util.opinion.LoaderService;
import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.Project; import ghidra.framework.model.Project;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.InvalidNameException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.*; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator { public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {
@ -301,20 +300,14 @@ public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {
} }
private void importFile(File file) throws CancelledException, DuplicateNameException, private void importFile(File file) throws CancelledException, VersionException, IOException {
InvalidNameException, VersionException, IOException {
String programNameOverride = null;
Project project = env.getProject(); Project project = env.getProject();
LoadResults<Program> loadResults = AutoImporter.importFresh(file, project, try (LoadResults<Program> loadResults = ProgramLoader.builder()
project.getProjectData().getRootFolder().getPathname(), this, new MessageLog(), .source(file)
TaskMonitor.DUMMY, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, .project(project)
programNameOverride, OptionChooser.DEFAULT_OPTIONS); .projectFolderPath(project.getProjectData().getRootFolder().getPathname())
.load()) {
try { loadResults.getPrimary().save(TaskMonitor.DUMMY);
loadResults.getPrimary().save(project, new MessageLog(), TaskMonitor.DUMMY);
}
finally {
loadResults.release(this);
} }
} }