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

View file

@ -43,8 +43,7 @@ import ghidra.app.tablechooser.TableChooserExecutor;
import ghidra.app.util.demangler.DemangledObject;
import ghidra.app.util.demangler.DemanglerUtil;
import ghidra.app.util.dialog.AskAddrDialog;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.*;
import ghidra.app.util.query.TableService;
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
* 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
* directly called.
* null is returned. For more control over the import process, {@link ProgramLoader} may be
* directly used.
* <p>
* NOTE: The returned {@link Program} is not automatically saved into the current project.
* <p>
@ -3634,11 +3633,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @throws Exception if any exceptions occur while importing
*/
public Program importFile(File file) throws Exception {
try {
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(file,
state.getProject(), null, this, new MessageLog(), monitor);
loadResults.releaseNonPrimary(this);
return loadResults.getPrimaryDomainObject();
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(state.getProject())
.monitor(monitor)
.load()) {
return loadResults.getPrimaryDomainObject(this);
}
catch (LoadException e) {
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,
* {@link AutoImporter} may be directly called.
* {@link ProgramLoader} may be directly used.
* <p>
* 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
@ -3661,10 +3661,15 @@ public abstract class GhidraScript extends FlatProgramAPI {
*/
public Program importFileAsBinary(File file, Language language, CompilerSpec compilerSpec)
throws Exception {
try {
Loaded<Program> loaded = AutoImporter.importAsBinary(file, state.getProject(), null,
language, compilerSpec, this, new MessageLog(), monitor);
return loaded.getDomainObject();
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(state.getProject())
.loaders(BinaryLoader.class)
.language(language)
.compiler(compilerSpec)
.monitor(monitor)
.load()) {
return loadResults.getPrimaryDomainObject(this);
}
catch (LoadException e) {
return null;

View file

@ -45,8 +45,8 @@ public class AnalyzeHeadless implements GhidraLaunchable {
//@formatter:off
IMPORT("-import", true, "[<directory>|<file>]+"),
PROCESS("-process", true, "[<project_file>]"),
PRE_SCRIPT("-prescript", true, "<ScriptName>"),
POST_SCRIPT("-postscript", true, "<ScriptName>"),
PRE_SCRIPT("-preScript", true, "<ScriptName>"),
POST_SCRIPT("-postScript", true, "<ScriptName>"),
SCRIPT_PATH("-scriptPath", true, "\"<path1>[;<path2>...]\""),
PROPERTIES_PATH("-propertiesPath", true, "\"<path1>[;<path2>...]\""),
SCRIPT_LOG("-scriptlog", true, "<path to script log file>"),
@ -54,7 +54,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
OVERWRITE("-overwrite", false),
RECURSIVE("-recursive", false),
READ_ONLY("-readOnly", false),
DELETE_PROJECT("-deleteproject", false),
DELETE_PROJECT("-deleteProject", false),
NO_ANALYSIS("-noanalysis", false),
PROCESSOR("-processor", true, "<languageID>"),
CSPEC("-cspec", true, "<compilerSpecID>"),
@ -66,7 +66,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
OK_TO_DELETE("-okToDelete", false),
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
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>") {
@Override
public boolean matches(String arg) {
@ -424,7 +424,8 @@ public class AnalyzeHeadless implements GhidraLaunchable {
options.setPostScriptsWithArgs(postScripts);
// Set loader and loader args
options.setLoader(loaderName, loaderArgs);
options.setLoader(loaderName);
options.setLoaderArgs(loaderArgs);
// Set user-specified language and compiler spec
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.script.*;
import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.*;
@ -1529,17 +1528,23 @@ public class HeadlessAnalyzer {
Msg.info(this, "IMPORTING: " + fsrl);
LoadResults<Program> loadResults = null;
Loaded<Program> primary = null;
try {
// Perform the load. Note that loading 1 file may result in more than 1 thing getting
// loaded.
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");
primary = loadResults.getPrimary();
Program primaryProgram = primary.getDomainObject();
Loaded<Program> primary = loadResults.getPrimary();
primaryProgram = primary.getDomainObject(this);
// Make sure we are allowed to save ALL programs to the project. If not, save none and
// fail.
@ -1565,7 +1570,15 @@ public class HeadlessAnalyzer {
// The act of marking the program as temporary by a script will signal
// us to discard any changes
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.
@ -1581,38 +1594,48 @@ public class HeadlessAnalyzer {
// Save
for (Loaded<Program> loaded : loadResults) {
if (!loaded.getDomainObject().isTemporary()) {
try {
DomainFile domainFile =
loaded.save(project, new MessageLog(), TaskMonitor.DUMMY);
Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)", loaded,
domainFile));
}
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);
Program program = loaded.getDomainObject(this);
try {
if (!program.isTemporary()) {
try {
DomainFile domainFile = loaded.save(TaskMonitor.DUMMY);
Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)",
loaded, domainFile));
}
catch (IOException e) {
Msg.info(this, "REPORT: Save failed for: " + loaded);
}
}
else {
Msg.info(this, "REPORT: Discarded file import as a result of script " +
"activity or analysis timeout: " + loaded);
if (options.readOnly) {
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
if (options.commit) {
for (Loaded<Program> loaded : loadResults) {
if (!loaded.getDomainObject().isTemporary()) {
if (loaded == primary) {
AutoAnalysisManager.getAnalysisManager(primaryProgram).dispose();
Program program = loaded.getDomainObject(this);
try {
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;
}
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" +
" processor/cspec arguments are compatible with the loader that is used during" +
" import and try again.");
@ -1638,37 +1661,12 @@ public class HeadlessAnalyzer {
return false;
}
finally {
if (loadResults != null) {
loadResults.release(this);
if (primaryProgram != null) {
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)
throws IOException {
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
* will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess"
* is made.
* name will attempt a "best-guess" if possible.
*
* @param loaderName The name (simple class name) of the loader to use.
* @param loaderArgs A list of loader-specific arguments. Could be null if there are none.
* @throws InvalidInputException if an invalid loader name was specified, or if loader arguments
* were specified but a loader was not.
* @param loaderName The name (simple class name) of the loader to use (could be null)
* @throws InvalidInputException if an invalid loader name was specified
*/
public void setLoader(String loaderName, List<Pair<String, String>> loaderArgs)
throws InvalidInputException {
public void setLoader(String loaderName) throws InvalidInputException {
if (loaderName != null) {
this.loaderClass = LoaderService.getLoaderClassByName(loaderName);
if (this.loaderClass == null) {
throw new InvalidInputException("Invalid loader name specified: " + loaderName);
}
this.loaderArgs = loaderArgs;
}
else {
if (loaderArgs != null && loaderArgs.size() > 0) {
throw new InvalidInputException(
"Loader arguments defined without a loader being specified.");
}
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.IOException;
import java.util.*;
import java.util.List;
import java.util.function.Predicate;
import generic.stl.Pair;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
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.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
* 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 {
private AutoImporter() {
// service class; cannot instantiate
@ -50,10 +49,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param file The {@link File} to import
* @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
* should be queried for their true project folder paths using
* {@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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException {
return importByUsingBestGuess(fileToFsrl(file), project, projectFolderPath, consumer,
messageLog, monitor);
return ProgramLoader.builder()
.source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
/**
@ -91,10 +99,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param fsrl The {@link FSRL} to import
* @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
* should be queried for their true project folder paths using
* {@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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException {
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor,
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null,
OptionChooser.DEFAULT_OPTIONS);
return ProgramLoader.builder()
.source(fsrl)
.project(project)
.projectFolderPath(projectFolderPath)
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
/**
@ -133,10 +149,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param provider The bytes to import
* @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
* should be queried for their true project folder paths using
* {@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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingBestGuess(ByteProvider provider,
Project project, String projectFolderPath, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException {
return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor,
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null,
OptionChooser.DEFAULT_OPTIONS);
return ProgramLoader.builder()
.source(provider)
.project(project)
.projectFolderPath(projectFolderPath)
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
/**
@ -174,10 +198,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param file The {@link File} to import
* @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()}.
* @param loaderClass The {@link Loader} class to use
* @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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @throws LoadException if nothing was loaded
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClass(File file,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException {
return importByUsingSpecificLoaderClass(fileToFsrl(file), project, projectFolderPath,
loaderClass, loaderArgs, consumer, messageLog, monitor);
return ProgramLoader.builder()
.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>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param fsrl The {@link FSRL} to import
* @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()}.
* @param loaderClass The {@link Loader} class to use
* @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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException {
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs);
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor,
loaderFilter, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null,
new LoaderArgsOptionChooser(loaderFilter));
return ProgramLoader.builder()
.source(fsrl)
.project(project)
.projectFolderPath(projectFolderPath)
.loaders(loaderClass)
.loaderArgs(loaderArgs)
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
/**
@ -263,10 +307,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param file The {@link File} to import
* @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()}.
* @param language The desired {@link Language}
* @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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException {
return importByLookingForLcs(fileToFsrl(file), project, projectFolderPath, language,
compilerSpec, consumer, messageLog, monitor);
return ProgramLoader.builder()
.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>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param fsrl The {@link FSRL} to import
* @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()}.
* @param language The desired {@link Language}
* @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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException {
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor,
LoaderService.ACCEPT_ALL, new LcsHintLoadSpecChooser(language, compilerSpec), null,
OptionChooser.DEFAULT_OPTIONS);
return ProgramLoader.builder()
.source(fsrl)
.project(project)
.projectFolderPath(projectFolderPath)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
/**
@ -350,10 +415,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param file The {@link File} to import
* @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 language The desired {@link Language}
* @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 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)
* @throws IOException if there was an IO-related problem loading
* @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 VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(File file,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException {
return importByUsingSpecificLoaderClassAndLcs(fileToFsrl(file), project, projectFolderPath,
loaderClass, loaderArgs, language, compilerSpec, consumer, messageLog, monitor);
return ProgramLoader.builder()
.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>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param fsrl The {@link FSRL} to import
* @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 language The desired {@link Language}
* @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 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)
* @throws IOException if there was an IO-related problem loading
* @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 VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @deprecated Use {@link ProgramLoader}
*/
@Deprecated(since = "11.5", forRemoval = true)
public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(FSRL fsrl,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException {
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs);
return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor,
loaderFilter, new LcsHintLoadSpecChooser(language, compilerSpec), null,
new LoaderArgsOptionChooser(loaderFilter));
return ProgramLoader.builder()
.source(fsrl)
.project(project)
.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
* language and compiler specification.
* <p>
* 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
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}).
* {@link Loaded#save(TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program} with {@link Loaded#release(Object)} when it is no longer needed.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program} with {@link Loaded#close()} when it is no longer needed.
*
* @param file The {@link File} to import
* @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()}.
* @param language The desired {@link Language}
* @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 monitor A task monitor
* @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
* failed language upgrade
* @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,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException {
LoadResults<Program> loadResults = importFresh(file, project, projectFolderPath, consumer,
messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec),
null, OptionChooser.DEFAULT_OPTIONS);
loadResults.releaseNonPrimary(consumer);
LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(project)
.projectFolderPath(projectFolderPath)
.loaders(BinaryLoader.class)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
loadResults.getNonPrimary().forEach(Loaded::close);
return loadResults.getPrimary();
}
@ -490,10 +587,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}).
* {@link Loaded#save(TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program} with {@link Loaded#release(Object)} when it is no longer needed.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program} with {@link Loaded#close()} when it is no longer needed.
*
* @param bytes The bytes to import
* @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()}.
* @param language The desired {@link Language}
* @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 monitor A task monitor
* @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
* failed language upgrade
* @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,
String projectFolderPath, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException {
LoadResults<Program> loadResults = importFresh(bytes, project, projectFolderPath, consumer,
messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec),
null, OptionChooser.DEFAULT_OPTIONS);
loadResults.releaseNonPrimary(consumer);
LoadResults<Program> loadResults = ProgramLoader.builder()
.source(bytes)
.project(project)
.projectFolderPath(projectFolderPath)
.loaders(BinaryLoader.class)
.language(language)
.compiler(compilerSpec)
.log(messageLog)
.monitor(monitor)
.load(consumer);
loadResults.getNonPrimary().forEach(Loaded::close);
return loadResults.getPrimary();
}
@ -535,10 +643,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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 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.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* 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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException {
return importFresh(fileToFsrl(file), project, projectFolderPath, consumer, messageLog,
monitor, loaderFilter, loadSpecChooser, importNameOverride, optionChooser);
return ProgramLoader.builder()
.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>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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 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.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* 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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException {
if (fsrl == null) {
throw new LoadException("Cannot load null fsrl");
}
try (ByteProvider provider =
FileSystemService.getInstance().getByteProvider(fsrl, true, monitor)) {
return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor,
loaderFilter, loadSpecChooser, importNameOverride, optionChooser);
}
return ProgramLoader.builder()
.source(fsrl)
.project(project)
.projectFolderPath(projectFolderPath)
.name(importNameOverride)
.loaders(loaderFilter)
.loaderArgs(optionChooser.getArgs())
.language(loadSpecChooser.getLanguageId())
.compiler(loadSpecChooser.getCompilerSpecId())
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
/**
@ -640,10 +769,10 @@ public final class AutoImporter {
* <p>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(TaskMonitor)}).
* <p>
* 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 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.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* 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 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)
* @throws IOException if there was an IO-related problem loading
* @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
* failed language upgrade
* @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,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException {
if (provider == null) {
throw new LoadException("Cannot load null provider");
}
// Get the load spec
LoadSpec loadSpec = getLoadSpec(loaderFilter, loadSpecChooser, provider);
if (loadSpec == null) {
throw new LoadException("No load spec found");
}
// Get the preferred import name
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);
return ProgramLoader.builder()
.source(provider)
.project(project)
.projectFolderPath(projectFolderPath)
.name(importNameOverride)
.loaders(loaderFilter)
.loaderArgs(optionChooser.getArgs())
.language(loadSpecChooser.getLanguageId())
.compiler(loadSpecChooser.getCompilerSpecId())
.log(messageLog)
.monitor(monitor)
.load(consumer);
}
}

View file

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

View file

@ -16,9 +16,11 @@
package ghidra.app.util.importer;
import java.util.Collection;
import java.util.Objects;
import ghidra.app.util.opinion.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.Msg;
import util.CollectionUtils;
@ -37,13 +39,46 @@ public class LcsHintLoadSpecChooser implements LoadSpecChooser {
* {@link CompilerSpec}.
*
* @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) {
this.languageID = language.getLanguageID();
this.compilerSpecID =
(compilerSpec == null) ? language.getDefaultCompilerSpec().getCompilerSpecID()
: compilerSpec.getCompilerSpecID();
public LcsHintLoadSpecChooser(Language language, CompilerSpec compilerSpec)
throws LanguageNotFoundException {
this(language.getLanguageID(),
compilerSpec == null ? language.getDefaultCompilerSpec().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

View file

@ -16,6 +16,8 @@
package ghidra.app.util.importer;
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
@ -31,6 +33,26 @@ public interface LoadSpecChooser {
*/
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}
*

View file

@ -19,18 +19,29 @@ import java.util.List;
import generic.stl.Pair;
import ghidra.app.util.Option;
import ghidra.app.util.opinion.Loader;
import ghidra.program.model.address.AddressFactory;
import ghidra.util.Msg;
/**
* 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 {
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
@ -43,12 +54,12 @@ public class LoaderArgsOptionChooser implements OptionChooser {
if (option.getArg() != null && arg.equalsIgnoreCase(option.getArg())) {
Object oldVal = option.getValue();
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\")",
arg, option.getName(), oldVal, val));
}
else {
Msg.error(AutoImporter.class, String.format(
Msg.error(LoaderArgsOptionChooser.class, String.format(
"Failed to apply \"%s\" to \"%s\" (old: \"%s\", bad: \"%s\")", arg,
option.getName(), oldVal, val));
return null;
@ -58,11 +69,16 @@ public class LoaderArgsOptionChooser implements OptionChooser {
}
}
if (!foundIt) {
Msg.error(AutoImporter.class, "Loader does not support " + arg + " argument");
return null;
Msg.warn(LoaderArgsOptionChooser.class,
"Skipping unsupported " + arg + " argument");
}
}
}
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 generic.stl.Pair;
import ghidra.app.util.Option;
import ghidra.app.util.opinion.Loader;
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
public interface OptionChooser {
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);
/**
* 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;
import java.util.List;
import java.util.function.Predicate;
import generic.stl.Pair;
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> {
private final Class<? extends Loader> single;
private List<Pair<String, String>> loaderArgs;
/**
* Create a new single loader filter from the given loader class.
*
* @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) {
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
public boolean test(Loader loader) {
if (loader.getClass().equals(single)) {
return true;
}
return false;
return loader.getClass().equals(single);
}
}

View file

@ -31,6 +31,7 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.*;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Library;
@ -49,23 +50,51 @@ import ghidra.util.task.TaskMonitor;
*/
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";
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";
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";
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";
/**
* How many levels of libraries to load
*/
public static final String DEPTH_OPTION_NAME = "Recursive Library Load Depth";
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";
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
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)
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
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String loadedName,
Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
@ -101,21 +155,28 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (!shouldLoadOnlyLibraries(options)) {
program = doLoad(provider, loadedName, loadSpec, libraryNameList, options, consumer,
log, monitor);
loadedProgramList.add(new Loaded<>(program, loadedName, projectFolderPath));
loadedProgramList.add(
new Loaded<>(program, loadedName, project, projectFolderPath, consumer));
log.appendMsg("------------------------------------------------\n");
}
else if (project != null) {
ProjectData projectData = project.getProjectData();
DomainFile domainFile = projectData.getFile(projectFolderPath + "/" + loadedName);
else {
if (project == null) {
throw new LoadException("Cannot load only libraries...project is null");
}
DomainFile domainFile =
project.getProjectData().getFile(projectFolderPath + "/" + loadedName);
if (domainFile == null) {
throw new LoadException(
"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);
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));
}
@ -128,7 +189,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
finally {
if (!success) {
release(loadedProgramList, consumer);
loadedProgramList.forEach(Loaded::close);
}
}
}
@ -169,32 +230,45 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options, log);
List<LibrarySearchPath> searchPaths = getLibrarySearchPaths(
loadedPrograms.getFirst().getDomainObject(), loadSpec, options, log, monitor);
Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
List<LibrarySearchPath> searchPaths;
try {
searchPaths = getLibrarySearchPaths(firstProgram, loadSpec, options, log, monitor);
}
finally {
firstProgram.release(this);
}
List<Loaded<Program>> saveablePrograms =
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
List<Loaded<Program>> saveablePrograms = loadedPrograms
.stream()
.filter(loaded -> loaded.check(Predicate.not(Program::isTemporary)))
.toList();
monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.increment();
Program program = loadedProgram.getDomainObject();
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");
Program program = loadedProgram.getDomainObject(this);
try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, searchPaths,
options, monitor, log);
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 {
resolveExternalLibraries(program, saveablePrograms, searchFolders, searchPaths,
options, monitor, log);
}
finally {
program.endTransaction(id, true);
}
}
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,
Project project, List<Option> options, MessageLog log) {
List<DomainFolder> searchFolders = new ArrayList<>();
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder destSearchFolder =
getLibraryDestinationSearchFolder(project, destPath, options);
DomainFolder linkSearchFolder = getLinkSearchFolder(project,
loadedPrograms.getFirst().getDomainObject(), projectFolderPath, options, log);
Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add);
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add);
return searchFolders;
Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
try {
List<DomainFolder> searchFolders = new ArrayList<>();
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder destSearchFolder =
getLibraryDestinationSearchFolder(project, destPath, options);
DomainFolder linkSearchFolder =
getLinkSearchFolder(project, firstProgram, projectFolderPath, options, log);
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)) {
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(library, provider,
customSearchPaths, libraryDestFolderPath, unprocessed, depth,
project, customSearchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary == null) {
loadedLibrary = loadLibraryFromSearchPaths(library, provider, searchPaths,
libraryDestFolderPath, unprocessed, depth, desiredLoadSpec, options,
log, consumer, monitor);
loadedLibrary = loadLibraryFromSearchPaths(library, provider, project,
searchPaths, libraryDestFolderPath, unprocessed, depth, desiredLoadSpec,
options, log, consumer, monitor);
}
if (loadedLibrary != null) {
boolean discarding = !loadLibraries || unprocessedLibrary.discard();
loadedLibrary.setDiscard(discarding);
boolean temporary = !loadLibraries || unprocessedLibrary.temporary();
loadedLibrary.apply(p -> p.setTemporary(temporary));
loadedPrograms.add(loadedLibrary);
log.appendMsg(discarding ? "Library not saved to project."
log.appendMsg(temporary ? "Library not saved to project."
: "Saving library to: " + loadedLibrary);
}
log.appendMsg("------------------------------------------------\n");
@ -581,7 +661,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
finally {
if (!success) {
release(loadedPrograms, consumer);
loadedPrograms.forEach(Loaded::close);
}
Stream.of(customSearchPaths, searchPaths)
.flatMap(Collection::stream)
@ -600,6 +680,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*
* @param library The library to load
* @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 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
@ -617,7 +698,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException if the user cancelled the load
*/
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,
List<Option> options, MessageLog log, Object consumer, TaskMonitor monitor)
throws CancelledException, IOException {
@ -662,7 +743,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
folderPath = joinPaths(folderPath, FilenameUtils.getFullPath(library));
}
}
return new Loaded<Program>(libraryProgram, simpleLibraryName, folderPath);
return new Loaded<Program>(libraryProgram, simpleLibraryName, project, folderPath,
consumer);
}
}
finally {
@ -947,9 +1029,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param name The name of the library
* @param depth The recursive load depth of the library (based on the original binary being
* 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

View file

@ -131,12 +131,14 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
throws CancelledException, IOException {
if (shouldPerformOrdinalLookup(options)) {
List<Loaded<Program>> saveablePrograms =
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
List<Loaded<Program>> saveablePrograms = loadedPrograms
.stream()
.filter(loaded -> loaded.check(Predicate.not(Program::isTemporary)))
.toList();
monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.checkCancelled();
Program program = loadedProgram.getDomainObject();
Program program = loadedProgram.getDomainObject(this);
int id = program.startTransaction("Ordinal fixups");
try {
applyLibrarySymbols(program, messageLog, monitor);
@ -144,6 +146,7 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
finally {
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>
* 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
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}).
* {@link Loaded#save(TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link Loaded#release(Object)} when they are no longer
* needed.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link Loaded#close()} when they are no longer needed.
*
* @param provider The bytes to load.
* @param loadedName A suggested name for the primary {@link Loaded} {@link Program}.
@ -135,21 +134,26 @@ public abstract class AbstractProgramLoader implements Loader {
try {
for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.checkCancelled();
Program program = loadedProgram.getDomainObject();
applyProcessorLabels(options, program);
program.setEventsEnabled(true);
Program program = loadedProgram.getDomainObject(this);
try {
applyProcessorLabels(options, program);
program.setEventsEnabled(true);
}
finally {
program.release(this);
}
}
// Subclasses can perform custom post-load fix-ups
postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog, monitor);
// Discard unneeded programs
// Discard temporary programs
Iterator<Loaded<Program>> iter = loadedPrograms.iterator();
while (iter.hasNext()) {
Loaded<Program> loaded = iter.next();
if (loaded.shouldDiscard()) {
if (loaded.check(p -> p.isTemporary())) {
iter.remove();
loaded.release(consumer);
loaded.close();
}
}
@ -158,7 +162,7 @@ public abstract class AbstractProgramLoader implements Loader {
}
finally {
if (!success) {
release(loadedPrograms, consumer);
loadedPrograms.forEach(Loaded::close);
}
postLoadCleanup(success);
}
@ -463,18 +467,6 @@ public abstract class AbstractProgramLoader implements Loader {
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) {
int id = program.startTransaction("Finalize load");
try {

View file

@ -67,8 +67,8 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
Program program = createProgram(provider, programName, imageBaseAddr, getName(), language,
compilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<Program>(program, programName, programFolderPath));
List<Loaded<Program>> loadedList = List.of(
new Loaded<Program>(program, programName, project, programFolderPath, consumer));
int transactionID = program.startTransaction("Loading");
boolean success = false;
@ -81,7 +81,7 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
finally {
program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded
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,
importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false;
try {
@ -294,7 +294,7 @@ public class BinaryLoader extends AbstractProgramLoader {
}
finally {
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;
try (ExternalSymbolResolver esr = new ExternalSymbolResolver(projectData, monitor)) {
for (Loaded<Program> loadedProgram : loadedPrograms) {
esr.addProgramToFixup(
loadedProgram.getProjectFolderPath() + loadedProgram.getName(),
loadedProgram.getDomainObject());
}
loadedPrograms.forEach(p -> esr.addProgramToFixup(p));
esr.fixUnresolvedExternalSymbols();
esr.logInfo(messageLog::appendMsg, true);
}

View file

@ -60,7 +60,7 @@ public class GdtLoader implements Loader {
DataTypeArchive dtArchive =
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,

View file

@ -78,7 +78,7 @@ public class GzfLoader implements Loader {
throws IOException, CancelledException, VersionException {
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,

View file

@ -154,7 +154,7 @@ public class IntelHexLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false;
try {
loadInto(provider, loadSpec, options, log, prog, monitor);
@ -164,7 +164,7 @@ public class IntelHexLoader extends AbstractProgramLoader {
}
finally {
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.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
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
*/
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;
@ -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
* {@link Loaded} {@link DomainObject}s. The first entry in the {@link List} is assumed to be
* 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
* @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 name The name of the loaded {@link DomainObject}. If a
* {@link #save(Project, Object, MessageLog, TaskMonitor) save} occurs, this will attempted to
* be used for the resulting {@link DomainFile}'s name.
* {@link #save(TaskMonitor) save} occurs, this will attempted to be used for 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
* {@link #save(Project, Object, MessageLog, TaskMonitor) save} operation. If null or empty,
* the root project folder will be used.
* {@link #save(TaskMonitor) save} operation. If null or empty, the root project folder will
* 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) {
this(List.of(new Loaded<T>(domainObject, name, projectFolderPath)));
public LoadResults(T domainObject, String name, Project project, String 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
*
* @return The "primary" {@link Loaded} {@link DomainObject}
*/
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}
* implementation
* Gets the "non-primary" {@link Loaded} {@link DomainObject}s, whose meaning is defined by each
* {@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}
*/
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() {
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 DomainObject} to the given {@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}.
* {@link Loaded#save(TaskMonitor) Saves} each {@link Loaded} {@link DomainObject} to the given
* {@link Project}.
*
* @param project The {@link Project} to save to
* @param consumer the consumer
* @param messageLog The log
* @param monitor A cancelable task monitor
* @throws CancelledException if the operation was cancelled
* @throws IOException If there was a problem saving
* @see Loaded#save(Project, MessageLog, TaskMonitor)
* @throws IOException If there was a problem saving. A thrown exception may result in only some
* 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)
throws CancelledException, IOException {
boolean success = false;
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);
}
}
}
public void save(TaskMonitor monitor) throws CancelledException, IOException {
for (Loaded<T> loaded : loadedList) {
loaded.save(monitor);
}
}
/**
* Notify all of the {@link Loaded} {@link DomainObject}s that the specified consumer is no
* longer using them. When the last consumer invokes this method, the {@link Loaded}
* {@link DomainObject}s will be closed and will become invalid.
* Unsafely notifies all of the {@link Loaded} {@link DomainObject}s that the specified consumer
* is no longer using them. Temporarily exists to provide backwards compatibility.
*
* @param consumer the consumer
* @deprecated Use {@link #close()} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
public void release(Object consumer) {
loadedList.forEach(loaded -> loaded.release(consumer));
}
/**
* Notify the filtered {@link Loaded} {@link DomainObject}s that the specified consumer is no
* longer using them. When the last consumer invokes this method, the filtered {@link Loaded}
* {@link DomainObject}s will be closed and will become invalid.
* Unsafely notifies the filtered {@link Loaded} {@link DomainObject}s that the specified
* consumer is no longer using them. Temporarily exists to provide backwards compatibility.
*
* @param consumer the consumer
* @param filter a filter to apply to the {@link Loaded} {@link DomainObject}s prior to the
* release
* @deprecated Use {@link #close()} instead
*/
@Deprecated(since = "11.5", forRemoval = true)
public void release(Object consumer, Predicate<? super Loaded<T>> filter) {
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.
*
* @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) {
for (int i = 0; i < loadedList.size(); i++) {
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
public Iterator<Loaded<T>> iterator() {
return loadedList.iterator();

View file

@ -17,8 +17,9 @@ package ghidra.app.util.opinion;
import java.io.FileNotFoundException;
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.util.InvalidNameException;
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
* {@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.
* <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
*/
public class Loaded<T extends DomainObject> {
public class Loaded<T extends DomainObject> implements AutoCloseable {
private final T domainObject;
private final String name;
private String projectFolderPath;
protected final T domainObject;
protected final String name;
protected Project project;
protected String projectFolderPath;
protected Object loadedConsumer;
private DomainFile domainFile;
private boolean ignoreSave;
private boolean discard;
protected DomainFile domainFile;
/**
* 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 name The name of the loaded {@link DomainObject}. If a
* {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for
* the resulting {@link DomainFile}'s name.
* @param name 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.
* @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
* {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root
* project folder will be used.
* {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be
* 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.name = name;
this.project = project;
this.loadedConsumer = consumer;
setProjectFolderPath(projectFolderPath);
}
/**
* Creates a {@link Loaded} view on an existing {@link DomainFile}. This type of {@link Loaded}
* object cannot be saved.
* Gets the loaded {@link DomainObject}.
* <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 domainFile The {@link DomainFile} to be loaded
* @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
* {@link Loaded} must also still be {@link #close() closed}).
* @return The loaded {@link DomainObject}
*/
public Loaded(T domainObject, DomainFile domainFile) {
this(domainObject, domainFile.getName(), domainFile.getParent().getPathname());
this.domainFile = domainFile;
this.ignoreSave = true;
public T getDomainObject(Object consumer) {
domainObject.addConsumer(consumer);
return domainObject;
}
/**
* 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}
* @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() {
return domainObject;
}
/**
* Gets the name of the loaded {@link DomainObject}. If a
* {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for
* the resulting {@link DomainFile}'s name.
* Gets the loaded {@link DomainObject}'s type
*
* @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}
*/
@ -95,8 +147,18 @@ public class Loaded<T extends DomainObject> {
}
/**
* Gets the project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation.
* Gets the {@link Project} this will get saved to during a {@link #save(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>
* 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
* {@link #save(Project, MessageLog, TaskMonitor)} operation.
* Sets the project folder path this will get saved to during a {@link #save(TaskMonitor)}
* operation.
*
* @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
* project folder will be used.
* {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be
* used.
*/
public void setProjectFolderPath(String projectFolderPath) {
if (projectFolderPath == null || projectFolderPath.isBlank()) {
@ -124,19 +186,6 @@ public class Loaded<T extends DomainObject> {
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
* 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
* 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
* @return The {@link DomainFile} where the save happened
* @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
* 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 {
if (ignoreSave) {
return domainFile;
}
if (domainObject.isClosed()) {
throw new ClosedException(
"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
* {@link #save(Project, MessageLog, TaskMonitor) saved}
* {@link #save(TaskMonitor) saved}
*
* @return The loaded {@link DomainObject}'s associated saved {@link DomainFile}, or null if
* was not saved
* @throws FileNotFoundException If the loaded {@link DomainObject} was saved but the associated
* {@link DomainFile} no longer exists
* @see #save(Project, MessageLog, TaskMonitor)
* @see #save(TaskMonitor)
*/
public DomainFile getSavedDomainFile() throws FileNotFoundException {
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)
*
* @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.
* Unsafely notifies the loaded {@link DomainObject} that the specified consumer is no longer
* using it. Temporarily exists to provide backwards compatibility.
*
* @param consumer the consumer
* @throws IOException If there was an issue deleting the saved {@link DomainFile}
* @see #save(Project, MessageLog, TaskMonitor)
* @deprecated Use {@link #close()} instead
*/
void deleteSavedDomainFile(Object consumer) throws IOException {
if (domainFile != null && domainFile.exists()) {
domainFile.delete();
domainFile = null;
@Deprecated(since = "11.5", forRemoval = true)
public void release(Object consumer) {
if (!domainObject.isClosed() && domainObject.isUsedBy(consumer)) {
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>
* 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
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* {@link LoadResults#save(boolean, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link DomainObject}s with {@link LoadResults#release(Object)} when they are no longer
* needed.
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link DomainObject}s with {@link LoadResults#close()} when they are no longer needed.
*
* @param provider The bytes to load.
* @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 options The load options.
* @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.
* @return The {@link LoadResults} which contains one or more {@link Loaded}
* {@link DomainObject}s (created but not saved).

View file

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

View file

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

View file

@ -122,7 +122,7 @@ public class MzLoader extends AbstractLibrarySupportLoader {
@Override
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,
importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
boolean success = false;
try {
success = doImport(result.lastXmlMgr, options, log, prog, monitor, false);
@ -212,7 +212,7 @@ public class XmlLoader extends AbstractProgramLoader {
}
finally {
if (!success) {
release(loadedList, consumer);
loadedList.forEach(Loaded::close);
}
}
}

View file

@ -22,7 +22,7 @@ import java.util.HashMap;
import java.util.Iterator;
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.framework.Application;
import ghidra.framework.client.*;
@ -573,7 +573,7 @@ public class GhidraProject {
* @param program
* 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);
cmd.applyTo(program);
mgr.initializeOptions();
@ -593,67 +593,196 @@ public class GhidraProject {
openPrograms.put(program, id);
}
public Program importProgram(File file, Language language,
CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException,
InvalidNameException, VersionException, IOException {
MessageLog messageLog = new MessageLog();
LoadResults<Program> loadResults = AutoImporter.importByLookingForLcs(file, project, null,
language, compilerSpec, this, messageLog, MONITOR);
Program program = loadResults.getPrimaryDomainObject();
loadResults.releaseNonPrimary(this);
initializeProgram(program, false);
return program;
/**
* Automatically imports the given {@link File} with the best matching {@link Loader} that
* supports the given language and 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 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,
DuplicateNameException, InvalidNameException, VersionException, IOException {
VersionException, LanguageNotFoundException, LoadException, IOException {
LanguageService svc = DefaultLanguageService.getLanguageService();
Language language = svc.getDefaultLanguage(processor);
CompilerSpec compilerSpec = language.getDefaultCompilerSpec();
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)
throws CancelledException, DuplicateNameException, InvalidNameException,
VersionException, IOException {
MessageLog messageLog = new MessageLog();
LoadResults<Program> loadResults = AutoImporter.importByUsingSpecificLoaderClass(file,
project, null, loaderClass, null, this, messageLog, MONITOR);
Program program = loadResults.getPrimaryDomainObject();
loadResults.releaseNonPrimary(this);
initializeProgram(program, false);
return program;
throws CancelledException, VersionException, LanguageNotFoundException, LoadException,
IOException {
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(project)
.loaders(loaderClass)
.monitor(MONITOR)
.load()) {
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,
CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException,
InvalidNameException, VersionException, IOException {
MessageLog messageLog = new MessageLog();
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, null);
LcsHintLoadSpecChooser opinionChoose = new LcsHintLoadSpecChooser(language, compilerSpec);
LoadResults<Program> loadResults =
AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR, loaderFilter,
opinionChoose, null, new LoaderArgsOptionChooser(loaderFilter));
loadResults.releaseNonPrimary(this);
return loadResults.getPrimaryDomainObject();
CompilerSpec compilerSpec) throws CancelledException, VersionException,
LanguageNotFoundException, LoadException, IOException {
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(project)
.loaders(loaderClass)
.language(language)
.compiler(compilerSpec)
.monitor(MONITOR)
.load()) {
Program program = loadResults.getPrimaryDomainObject(this);
initializeProgram(program, false);
return program;
}
}
public Program importProgram(File file) throws CancelledException,
DuplicateNameException, InvalidNameException, VersionException, IOException {
MessageLog messageLog = new MessageLog();
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(file, project,
null, this, messageLog, MONITOR);
Program program = loadResults.getPrimaryDomainObject();
loadResults.releaseNonPrimary(this);
initializeProgram(program, false);
return program;
/**
* Automatically imports the given {@link File}.
* <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
* @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 {
Program program = importByStealingCodeFromAutoImporterByUsingBestGuess(file);
initializeProgram(program, false);
return program;
/**
* Automatically imports the given {@link File}.
* <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
* @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
//==================================================================================================

View file

@ -525,7 +525,7 @@ public class ImporterPlugin extends Plugin
Program program = manager.getCurrentProgram();
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);
@ -279,9 +287,25 @@ public class ImporterUtilities {
try {
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);
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 =
loadSpec.getLanguageCompilerSpec().getLanguage().getAddressFactory();
SystemUtilities.runSwingLater(() -> {
OptionsDialog dialog = new LoadLibrariesOptionsDialog(provider, program, tool,
loadSpec, () -> addressFactory);
@ -345,20 +369,12 @@ public class ImporterUtilities {
Program program =
doFSImportHelper((GFileSystemProgramProvider) refdFile.fsRef.getFilesystem(),
gfile, destFolder, consumer, monitor);
if (program != null) {
LoadResults<? extends DomainObject> loadResults = new LoadResults<>(program,
program.getName(), destFolder.getPathname());
boolean success = false;
try {
doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer,
"", monitor);
success = true;
}
finally {
if (!success) {
program.release(consumer);
}
}
if (program == null) {
return;
}
try (LoadResults<? extends DomainObject> loadResults = new LoadResults<>(program,
program.getName(), tool.getProject(), destFolder.getPathname(), consumer)) {
doPostImportProcessing(tool, programManager, fsrl, loadResults, "", monitor);
}
}
catch (Exception e) {
@ -420,14 +436,14 @@ public class ImporterUtilities {
Object consumer = new Object();
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,
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) {
// no need to show a message
@ -440,35 +456,39 @@ public class ImporterUtilities {
private static Set<DomainFile> doPostImportProcessing(PluginTool pluginTool,
ProgramManager programManager, FSRL fsrl,
LoadResults<? extends DomainObject> loadResults, Object consumer, String importMessages,
LoadResults<? extends DomainObject> loadResults, String importMessages,
TaskMonitor monitor) throws CancelledException {
boolean firstProgram = true;
Set<DomainFile> importedFilesSet = new HashSet<>();
for (Loaded<? extends DomainObject> loaded : loadResults) {
monitor.checkCancelled();
if (loaded.getDomainObject() instanceof Program program) {
if (programManager != null) {
int openState = firstProgram
? ProgramManager.OPEN_CURRENT
: ProgramManager.OPEN_VISIBLE;
programManager.openProgram(program, openState);
Object consumer = new Object();
DomainObject obj = loaded.getDomainObject(consumer);
try {
if (obj instanceof Program) {
if (programManager != null) {
int openState = firstProgram
? 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
displayResults(pluginTool, loaded.getDomainObject(),
loaded.getDomainObject().getDomainFile(), importMessages);
if (firstProgram) {
// currently we only show results for the imported program, not any libraries
displayResults(pluginTool, obj, obj.getDomainFile(), importMessages);
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && !importMessages.isEmpty()) {
Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages);
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && !importMessages.isEmpty()) {
Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages);
}
}
firstProgram = false;
}
finally {
obj.release(consumer);
}
loaded.release(consumer);
firstProgram = false;
}
selectFiles(importedFilesSet);

View file

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

View file

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

View file

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

View file

@ -21,6 +21,7 @@ import java.util.*;
import java.util.function.Consumer;
import db.Transaction;
import ghidra.app.util.opinion.Loaded;
import ghidra.framework.model.*;
import ghidra.framework.options.Options;
import ghidra.program.model.listing.*;
@ -68,40 +69,33 @@ public class ExternalSymbolResolver implements Closeable {
* is called.
* <p>
* 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
* be saved to.
* {@link #addProgramToFixup(Loaded)}.
*
* @param program {@link Program} to fix
*/
public void addProgramToFixup(Program program) {
addProgramToFixup(program.getDomainFile().getPathname(), program);
}
/**
* 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) {
String programPath = program.getDomainFile().getPathname();
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) {
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.
*
@ -122,7 +116,7 @@ public class ExternalSymbolResolver implements Closeable {
/**
* 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
*/
@ -152,7 +146,7 @@ public class ExternalSymbolResolver implements Closeable {
* released during {@link #close()} of this ExternalSymbolServer instance.
* <p>
* 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
* @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 {
if (!success) {
release(allLoadedPrograms, consumer);
allLoadedPrograms.forEach(Loaded::close);
}
}
if (allLoadedPrograms.isEmpty()) {
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;
}
@ -300,11 +300,9 @@ public class ApkLoader extends DexLoader {
* @param log the message log
* @param monitor the task monitor
*/
private void link(List<Program> programList, MessageLog log, TaskMonitor monitor) {
MultiDexLinker linker = new MultiDexLinker(programList);
try {
private void link(List<Loaded<Program>> programList, MessageLog log, TaskMonitor monitor) {
try (MultiDexLinker linker = new MultiDexLinker(programList)) {
linker.link(monitor);
linker.clear(monitor);
}
catch (Exception e) {
log.appendException(e);

View file

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

View file

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

View file

@ -53,7 +53,6 @@ import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.function.Predicate;
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.archive.CoffArchiveHeader;
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.framework.model.*;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
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) {
// 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)
throws CancelledException, DuplicateNameException, InvalidNameException,
VersionException, IOException {
throws CancelledException, InvalidNameException, VersionException, IOException {
try (RandomAccessByteProvider provider = new RandomAccessByteProvider(file)) {
if (!CoffArchiveHeader.isMatch(provider)) {
return;
@ -196,30 +194,34 @@ public class MSLibBatchImportWorker extends GhidraScript {
Pair<DomainFolder, String> pair =
getFolderAndUniqueFile(currentLibraryFolder, preferredName);
LoadResults<? extends DomainObject> loadResults = null;
try {
loadResults = AutoImporter.importFresh(coffProvider,
state.getProject(), pair.first.getPathname(), this, log,
monitor, LOADER_FILTER, LOADSPEC_CHOOSER, pair.second,
OptionChooser.DEFAULT_OPTIONS);
try (LoadResults<? extends DomainObject> loadResults =
ProgramLoader.builder()
.source(coffProvider)
.project(state.getProject())
.projectFolderPath(pair.first.getPathname())
.loaders(MSCoffLoader.class)
.compiler("windows")
.name(pair.second)
.log(log)
.monitor(monitor)
.load()) {
for (Loaded<? extends DomainObject> loaded : loadResults) {
if (loaded.getDomainObject() instanceof Program program) {
loaded.save(state.getProject(), log, monitor);
println(
"Imported " + program.getDomainFile().getPathname());
DomainFile progFile = program.getDomainFile();
if (!progFile.isVersioned()) {
progFile.addToVersionControl(initalCheckInComment,
false, monitor);
DomainObject obj = loaded.getDomainObject(this);
try {
if (obj instanceof Program) {
loaded.save(monitor);
DomainFile progFile = obj.getDomainFile();
println("Imported " + progFile.getPathname());
if (!progFile.isVersioned()) {
progFile.addToVersionControl(initalCheckInComment,
false, monitor);
}
}
}
}
}
finally {
if (loadResults != null) {
loadResults.release(this);
finally {
obj.release(this);
}
}
}
}

View file

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

View file

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

View file

@ -20,8 +20,7 @@ import java.io.File;
import org.junit.*;
import db.Transaction;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadResults;
import ghidra.base.project.GhidraProject;
import ghidra.framework.Application;
@ -57,15 +56,19 @@ public class TutorialDebuggerMaintenance extends AbstractGhidraHeadedIntegration
@Test
public void testRecreateTermminesGzf() throws Throwable {
File termmines = Application.getModuleDataFile("TestResources", "termmines").getFile(false);
LoadResults<Program> results = AutoImporter.importByUsingBestGuess(termmines,
env.getProject(), "/", this, new MessageLog(), CONSOLE);
program = results.getPrimaryDomainObject();
try (Transaction tx = program.openTransaction("Analyze")) {
program.setExecutablePath("/tmp/termmines");
GhidraProject.analyze(program);
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(termmines)
.project(env.getProject())
.monitor(CONSOLE)
.load()) {
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;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
@ -74,8 +74,7 @@ import ghidra.app.plugin.core.terminal.TerminalProvider;
import ghidra.app.script.GhidraState;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadResults;
import ghidra.async.AsyncTestUtils;
import ghidra.debug.api.modules.ModuleMapProposal;
@ -389,13 +388,15 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator
protected Program importModule(TraceModule module) throws Throwable {
Program prog = null;
try {
long snap = flatDbg.getCurrentSnap();
MessageLog log = new MessageLog();
LoadResults<Program> result = AutoImporter.importByUsingBestGuess(
new File(module.getName(snap)), env.getProject(), "/", this, log, monitor);
result.save(env.getProject(), this, log, monitor);
prog = result.getPrimaryDomainObject();
long snap = flatDbg.getCurrentSnap();
try (LoadResults<Program> result = ProgramLoader.builder()
.source(new File(module.getName(snap)))
.project(env.getProject())
.monitor(monitor)
.load()) {
result.save(monitor);
prog = result.getPrimaryDomainObject(this);
GhidraProgramUtilities.markProgramNotToAskToAnalyze(prog);
programManager.openProgram(prog);
}

View file

@ -15,9 +15,8 @@
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assume.assumeTrue;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import java.nio.file.Paths;
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.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.services.TraceRmiLauncherService;
import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.LoadResults;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
@ -80,9 +78,13 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
@Test
public void testGetClassName() throws Exception {
ResourceFile rf = Application.getModuleDataFile("TestResources", "HelloWorld.class");
LoadResults<Program> results = AutoImporter.importByUsingBestGuess(rf.getFile(false),
env.getProject(), "/", this, new MessageLog(), monitor);
program = results.getPrimaryDomainObject();
try (LoadResults<Program> results = ProgramLoader.builder()
.source(rf.getFile(false))
.project(env.getProject())
.monitor(monitor)
.load()) {
program = results.getPrimaryDomainObject(this);
}
AutoAnalysisManager analyzer = AutoAnalysisManager.getAnalysisManager(program);
analyzer.reAnalyzeAll(null);
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 ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
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.LoaderService;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.Project;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {
@ -301,20 +300,14 @@ public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {
}
private void importFile(File file) throws CancelledException, DuplicateNameException,
InvalidNameException, VersionException, IOException {
String programNameOverride = null;
private void importFile(File file) throws CancelledException, VersionException, IOException {
Project project = env.getProject();
LoadResults<Program> loadResults = AutoImporter.importFresh(file, project,
project.getProjectData().getRootFolder().getPathname(), this, new MessageLog(),
TaskMonitor.DUMMY, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED,
programNameOverride, OptionChooser.DEFAULT_OPTIONS);
try {
loadResults.getPrimary().save(project, new MessageLog(), TaskMonitor.DUMMY);
}
finally {
loadResults.release(this);
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)
.project(project)
.projectFolderPath(project.getProjectData().getRootFolder().getPathname())
.load()) {
loadResults.getPrimary().save(TaskMonitor.DUMMY);
}
}