Merge remote-tracking branch 'origin/GP-4085_ghidra1_SharedVTSession_SQUASHED'

This commit is contained in:
ghidra1 2024-03-13 16:04:17 -04:00
commit 5fde26708e
38 changed files with 1273 additions and 603 deletions

View file

@ -14,7 +14,7 @@
</HEAD>
<BODY>
<H1>Project Repository</H1>
<H1><A name="ProjectRepository"></A>Project Repository</H1>
<P>Ghidra supports the concept of a <I>project repository</I> such that files in the repository
can be <I>versioned</I>.&nbsp; <A name="Versioning"></A>Versioning allows you to track file

View file

@ -424,7 +424,6 @@ public class HeadlessAnalyzer {
if (locator.getProjectDir().exists()) {
project = openProject(locator);
AppInfo.setActiveProject(project);
}
else {
if (options.runScriptsNoImport) {
@ -441,7 +440,6 @@ public class HeadlessAnalyzer {
Msg.info(this, "Creating " + (options.deleteProject ? "temporary " : "") +
"project: " + locator);
project = getProjectManager().createProject(locator, null, false);
AppInfo.setActiveProject(project);
}
try {
@ -459,7 +457,6 @@ public class HeadlessAnalyzer {
}
finally {
project.close();
AppInfo.setActiveProject(null);
if (!options.runScriptsNoImport && options.deleteProject) {
FileUtilities.deleteDir(locator.getProjectDir());
locator.getMarkerFile().delete();
@ -1841,11 +1838,13 @@ public class HeadlessAnalyzer {
HeadlessProject(HeadlessGhidraProjectManager projectManager, GhidraURLConnection connection)
throws IOException {
super(projectManager, connection);
AppInfo.setActiveProject(this);
}
HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator)
throws NotOwnerException, LockException, IOException {
super(projectManager, projectLocator, false);
AppInfo.setActiveProject(this);
}
}

View file

@ -45,7 +45,7 @@ import ghidra.util.task.TaskMonitor;
public class ProgramOpener {
private final Object consumer;
private String openPromptText = "Open";
private boolean silent = false; // if true operation does not permit interaction
private boolean silent = SystemUtilities.isInHeadlessMode(); // if true operation does not permit interaction
private boolean noCheckout = false; // if true operation should not perform optional checkout
/**
@ -253,8 +253,9 @@ public class ProgramOpener {
if (domainFile.checkout(dialog.exclusiveCheckout(), monitor)) {
return;
}
Msg.showError(this, null, "Checkout Failed", "Exclusive checkout failed for: " +
domainFile.getName() + "\nOne or more users have file checked out!");
Msg.showError(this, null, "Checkout Failed",
"Exclusive checkout failed for: " + domainFile.getName() +
"\nOne or more users have file checked out!");
}
catch (CancelledException e) {
// we don't care, the task has been cancelled

View file

@ -56,6 +56,7 @@ src/main/help/help/topics/VersionTrackingPlugin/images/VersionTrackingTool.png||
src/main/help/help/topics/VersionTrackingPlugin/images/accepted_fully_applied.png||GHIDRA||reviewed||END|
src/main/help/help/topics/VersionTrackingPlugin/images/accepted_fully_considered.png||GHIDRA||reviewed||END|
src/main/help/help/topics/VersionTrackingPlugin/images/accepted_warning.png||GHIDRA||reviewed||END|
src/main/help/help/topics/VersionTrackingPlugin/images/start-here.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Apply_Options.html||GHIDRA||||END|
src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Functions_Table.html||GHIDRA||||END|
src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Implied_Matches_Table.html||GHIDRA||||END|

View file

@ -0,0 +1,70 @@
/* ###
* 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.
*/
//Script that enables user to add an existing Version Tracking Session to version control. This
//is meant to to be used when project is a shared project and when running in headless mode
//since it is simple add a VTSession to version control from the project manager when running in
//GUI mode.
//@category Version Tracking
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.framework.model.DomainFile;
import ghidra.util.MessageType;
public class AddVTSessionToVersionControl extends GhidraScript {
@Override
public void run() throws Exception {
GhidraValuesMap startupValues = new GhidraValuesMap();
startupValues.defineProjectFile("Select Version Tracking Session", "/");
startupValues.defineString("Enter commit message", "Commiting session to version control");
startupValues.setValidator((valueMap, status) -> {
if (!valueMap.hasValue("Select Version Tracking Session")) {
status.setStatusText("Must select a Version Tracking Session!", MessageType.ERROR);
return false;
}
if (!valueMap.hasValue("Enter commit message")) {
status.setStatusText("Must enter a commit message!", MessageType.ERROR);
return false;
}
return true;
});
startupValues = askValues(
"Enter Version Tracking Session info for adding to source control:", "", startupValues);
DomainFile sessionDF = startupValues.getProjectFile("Select Version Tracking Session");
String commitMsg = startupValues.getString("Enter commit message");
if (sessionDF.isVersioned()) {
println("Chosen session is already in version control");
return;
}
// add session to version control and do not keep checkout out
sessionDF.addToVersionControl(commitMsg, false, monitor);
println(sessionDF.getName() + " was successfully added to version control.");
}
}

View file

@ -13,37 +13,78 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// A script that runs Auto Version Tracking given the options set in one of the following ways:
// A script that runs Auto Version Tracking such that the current program in the tool is the
// destination program and the user is prompted to choose the source program. The user must also
// choose a name for a new Version Tracking Session. The script cannot run using an existing session.
// There are many options that can be set in one of the following ways:
// 1. If script is run from the CodeBrowser, the GUI options are set in a pop up dialog by user.
// 2. If script is run in headless mode either the defaults provided by the script are used or the
// user can specify a script to be run that sets the options. See example script
// 2. If script is run in headless mode either the default options provided by the script are used
// or the user can specify a script to be run that sets the options. See example script
// SetAutoVersionTrackingOptionsScript that can be copied and updated to reflect the users
// desired options.
//
// NOTE: This is an example to show how run this script in headless mode
// HEADLESS MODE NON-SHARED PROJECT:
//
// This is an example to show how run this script in headless mode against a local non-shared
// project
//
// <ghidra_install>/support/analyzeHeadless.bat/sh c:/MyGhidraProjectFolder
// MyProjectName/OptionalFolderContainingProgram -process Program1.exe -postScript
// MyProjectName/OptionalFolderContainingDestProgram -process DestinationProgram.exe -postScript
// MyOptionsSetupScript -postScript AutoVersionTrackingScript.java "/FolderContainingSession"
// "MySessionName" true "/OptionalFolderContainingProgram/Program2.exe"
// "MySessionName" "/OptionalFolderContainingSourceProgram/SourceProgram.exe"
//
//
// NOTE: The first program will be analyzed for you if it is not already analyzed (and if you
// do not include the -noanalysis option) as it is part of the typical analyzeHeadless run.
// The second program must be analyzed prior to running the script as the headless analyzer
// itself knows nothing about the file other than as a given option name. This is true in
// both GUI and headless mode.
//
// NOTE: The second to last parameter is to identify whether the first listed program
// is the source program or not. True means first program is source program and second
// program is destination program. False means second program is source program and first
// program is destination program. This is important if you want the correct markup to be
// applied from the source to destination program.
// NOTE: The destination program will be analyzed for you if it is not already analyzed (and if
// you do not include the -noanalysis option) as it is part of the typical analyzeHeadless
// run. The source program must be analyzed prior to running the script as the headless
// analyzer itself knows nothing about the file other than as a given option name. This is
// true in both GUI and headless mode.
//
// NOTE: The options setup script is optional. It is only necessary if users want to change the
// default options. To use it make a copy of the example one and save to a new script. You
// You may need to add the -scriptPath to the headless run so it will find your script.
// other default options that are not settable on the headless command line. To use an
// options script, make a copy of the example one (SetAutoVersionTrackingOptionsScript)
// and save it with a new script file name. Then use the -postScript headless argument to
// run the options script before the second -postScript argument to run the
// AutoVersionTrackingScript. Depending on where you save your script, you might also need
// to add the -scriptPath to the headless run so it will find your script.
//
// SHARED PROJECT MODE FROM GUI
//
// From the GUI, this script can run on local project files contained in the shared project or
// those that have been added to source control. If the destination program has been added to
// version control but is not checked out, the user will be prompted to checkout the file.
// If the file is not checked-out the script will not proceed. User is responsible for checking
// in changes to the destination file made by the script if they want the changes added. After
// the script is run, if user wants to add the session to version control they can.
//
// SHARED PROJECT MODE FROM HEADLESS MODE:
//
// If running this script in headless mode on a shared project, both source and destination
// programs must have already been added to version control before running the script or the
// script will not be able to locate the programs because the only programs visible to it will
// be those in the shared repository project.
//
// The headless shared project run will be different from the non-shared project in terms of how
// to tell it where the project location is. Instead of specifying a project location and
// project name, the user must instead specify the Ghidra Server repository URL.
//
// Also, there are necessary extra arguments to the headless run in order to connect to the
// server and commit the destination program changes to the server.
//
// This is an example command line for running this script in headless shared project mode:
//
// <ghidra_install>/support/analyzeHeadless.bat/sh
// ghidra://localhost:13100/MyProjectName/OptionalFolderContainingDestProgram -process
// DestinationProgram.exe -postScript MyOptionsSetupScript -postScript
// AutoVersionTrackingScript.java "/FolderContainingSession"
// "MySessionName" "/OptionalFolderContainingSourceProgram/SourceProgram.exe"
// optionalAddSessionToVersionControl -connect username -p -commit "my commit msg"
//
// NOTE: The Destination program being processed in the shared project headless run must not be
// checked out by anyone prior to the run. The headless script expects the file to be in version
// control but not checked out. The headless script will check it out, run the given scripts
// against it then check in any changes with the given commit message.
//
//@category Version Tracking
import ghidra.app.script.GhidraScript;
import ghidra.feature.vt.api.db.VTSessionDB;
@ -62,29 +103,29 @@ import ghidra.util.task.TaskLauncher;
public class AutoVersionTrackingScript extends GhidraScript {
private Program sourceProgram;
private Program destinationProgram;
@Override
public void cleanup(boolean success) {
if (sourceProgram != null && sourceProgram.isUsedBy(this)) {
sourceProgram.release(this);
}
if (destinationProgram != null && destinationProgram.isUsedBy(this)) {
destinationProgram.release(this);
}
super.cleanup(success);
}
private static final int NUM_ARGS = 3;
@Override
public void run() throws Exception {
if(currentProgram == null) {
println("Please open the destination program.");
return;
}
Program destinationProgram = currentProgram;
if (!destinationProgram.canSave()) {
println("VT Session destination program " + destinationProgram.getName() +
" is read-only which prevents its use.");
return;
}
GhidraValuesMap startupValues = new GhidraValuesMap();
startupValues.defineProjectFolder("Version Tracking Session Folder", "/");
startupValues.defineString("Version Tracking Session Name");
startupValues.defineBoolean("Check if current program is the Source Program", true);
startupValues.defineProgram("Please select the other program");
startupValues.defineProjectFile("Please select the SOURCE program", "/");
startupValues.setValidator((valueMap, status) -> {
@ -107,89 +148,105 @@ public class AutoVersionTrackingScript extends GhidraScript {
return false;
}
if (!valueMap.hasValue("Please select the other program")) {
status.setStatusText("Must choose second program!", MessageType.ERROR);
if (!valueMap.hasValue("Please select the SOURCE program")) {
status.setStatusText("Must choose a SOURCE program!", MessageType.ERROR);
return false;
}
return true;
});
startupValues = askValues("Enter Auto Version Tracking Information",
"Changing these options will not change the corresponding tool options", startupValues);
"The currently opened program is assumed to be the DESTINATION program.",
startupValues);
DomainFolder folder = startupValues.getProjectFolder("Version Tracking Session Folder");
String name = startupValues.getString("Version Tracking Session Name");
boolean isCurrentProgramSourceProg =
startupValues.getBoolean("Check if current program is the Source Program");
// setting auto upgrade to isHeadless, will cause headless uses to auto upgrade, but in
// Gui mode, will prompt before upgrading.
// GUI mode, will prompt before upgrading.
boolean autoUpgradeIfNeeded = isRunningHeadless();
Program otherProgram = startupValues.getProgram("Please select the other program", this,
state.getTool(), autoUpgradeIfNeeded);
if (isCurrentProgramSourceProg) {
sourceProgram = currentProgram;
destinationProgram = otherProgram;
}
else {
destinationProgram = currentProgram;
sourceProgram = otherProgram;
}
if (sourceProgram == null || destinationProgram == null) {
DomainFile sourceProgramDF =
startupValues.getProjectFile("Please select the SOURCE program");
if (!Program.class.isAssignableFrom(sourceProgramDF.getDomainObjectClass())) {
println(sourceProgramDF.getContentType() + " file " + sourceProgramDF.getName() +
" may not be specified as the SOURCE Program.");
return;
}
// Need to end the script transaction or it interferes with vt things that need locks
end(true);
Program sourceProgram = (Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded,
false, monitor);
VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
VTSession session = null;
try {
// Need to end the script transaction or it interferes with vt things that need locks
end(true);
if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor);
}
session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
// create a default options map in case cannot get user input
GhidraValuesMap optionsMap = createDefaultOptions();
// if running script in GUI get options from user and update the vtOptions with them
if (!isRunningHeadless()) {
optionsMap = getOptionsFromUser();
}
// else if running script in headless get possible options set by prescript that saves
// optionsMap in script state variable and update the vtOptions with them
else {
// try to get options map from state if running headless
// if user runs prescript to set up their own options map those options will be used
// See SetAutoVersionTrackingOptionsScript.java as an example
GhidraValuesMap stateOptionsMap =
(GhidraValuesMap) state.getEnvironmentVar("autoVTOptionsMap");
if (optionsMap != null) {
optionsMap = stateOptionsMap;
if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor);
}
}
// create a default options map in case cannot get user input
GhidraValuesMap optionsMap = createDefaultOptions();
ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap);
// if running script in GUI get options from user and update the vtOptions with them
if (!isRunningHeadless()) {
optionsMap = getOptionsFromUser();
AutoVersionTrackingTask autoVtTask = new AutoVersionTrackingTask(session, vtOptions);
}
// else if running script in headless get possible options set by prescript that saves
// optionsMap in script state variable and update the vtOptions with them
else {
// try to get options map from state if running headless
// if user runs prescript to set up their own options map those options will be used
// See SetAutoVersionTrackingOptionsScript.java as an example
GhidraValuesMap stateOptionsMap =
(GhidraValuesMap) state.getEnvironmentVar("autoVTOptionsMap");
if (stateOptionsMap != null) {
optionsMap = stateOptionsMap;
}
TaskLauncher.launch(autoVtTask);
}
// if not running headless user can decide whether to save or not
// if running headless - must save here or nothing that was done in this script will be
// accessible later.
if (isRunningHeadless()) {
otherProgram.save("Updated with Auto Version Tracking", monitor);
ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap);
AutoVersionTrackingTask autoVtTask = new AutoVersionTrackingTask(session, vtOptions);
TaskLauncher.launch(autoVtTask);
// Save destination program and session changes
destinationProgram.save("Updated with Auto Version Tracking", monitor);
session.save();
println(autoVtTask.getStatusMsg());
}
catch (CancelledException e) {
// let finally clean up
return;
}
finally {
if (sourceProgram != null) {
sourceProgram.release(this);
}
if (session != null) {
session.release(this);
}
}
// try adding to version control if it is a transient project (ie headless operating against
// a shared project repository
if (state.getProject().getProjectLocator().isTransient()) {
session.getDomainFile()
.addToVersionControl("Added new session + " + session.getName(), false,
monitor);
println("Added session " + session.getName() + " to version control.");
}
println(autoVtTask.getStatusMsg());
otherProgram.release(this);
}
/**

View file

@ -63,8 +63,7 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
return;
}
VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
VTSession session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
// it seems clunky to have to create this separately, but I'm not sure how else to do it
folder.createFile(name, session, monitor);

View file

@ -20,14 +20,14 @@
import java.util.Collection;
import java.util.Set;
import ghidra.feature.vt.GhidraVersionTrackingScript;
import ghidra.feature.vt.AbstractGhidraVersionTrackingScript;
import ghidra.feature.vt.api.main.VTMatch;
import ghidra.framework.model.Project;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
public class FindChangedFunctionsScript extends AbstractGhidraVersionTrackingScript {
private Program p1;
private Program p2;

View file

@ -20,10 +20,10 @@
import java.util.Collection;
import java.util.List;
import ghidra.feature.vt.GhidraVersionTrackingScript;
import ghidra.feature.vt.AbstractGhidraVersionTrackingScript;
import ghidra.feature.vt.api.main.*;
public class OpenVersionTrackingSessionScript extends GhidraVersionTrackingScript {
public class OpenVersionTrackingSessionScript extends AbstractGhidraVersionTrackingScript {
@Override
protected void run() throws Exception {
@ -33,6 +33,9 @@ public class OpenVersionTrackingSessionScript extends GhidraVersionTrackingScrip
}
private void acceptMatchesWithGoodConfidence() throws Exception {
VTSession vtSession = getVTSession();
println("Working on session: " + vtSession);
List<VTMatchSet> matchSets = vtSession.getMatchSets();

View file

@ -401,6 +401,56 @@
version tracking session.</P>
</BLOCKQUOTE>
<H2><A name="Open_Existing_Session"></A>Open an Existing Session</H2>
<BLOCKQUOTE>
<P>To open an existing session you can do one of the following:</P>
<UL>
<LI>Double click a an existing Session in the Project Manager window. Sessions can be
identified by the <IMG border="0" src="images/start-here.png"> icon next to their names.</LI>
<LI>Drag an existing Session onto a running tool.</LI>
<LI>Choose File->Open Session... from an open Version Tracking tool and select a Version Tracking Session.</LI>
</BLOCKQUOTE>
<H2><A name="Versioning_Sessions"></A>Sessions in Project Repositories</H2>
<BLOCKQUOTE>
<P>Sessions can be versioned using Ghidra's
<A href="help/topics/VersionControl/project_repository.htm#ProjectRepository">Project Repository</A>
mechanism mostly in the same way programs or data archives can be with some
qualifications:</P>
<UL>
<LI>Most shared project actions work for Sessions as they normally do, such as <b>Add to
Version Control</b>, <b>Check In</b>, <b>History</b>, ... </LI>
<LI>When checking out a Session you must choose exclusive checkout because there is no
way to merge two Sessions that have been edited. Exclusive checkout will prevent others
from making changes to the session until an Undo Checkout is performed.</LI>
<LI>In order for others to check out a Session that another user has added to source
control, the two programs used in creating the session must be first added to source
control. NOTE: The exact two programs used when creating the Session (not copies of the
programs) must be the ones added.</LI>
<LI>When opening a versioned session in the active project the user may be prompted to perform a checkout if not currently checked-out.</LI>
<LI>Opening a versioned session must have an exclusive checkout or it will fail on open. In addition, only a session within the active project may be opened (i.e., not a viewed project or URL-based access). </LI>
<LI>NOTE: A session and its two programs must reside within the same project.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE><!-- Main content blockquote -->
<P class="providedbyplugin">Provided by: <I>Version Tracking Plugin</I></P>

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

View file

@ -25,50 +25,81 @@ import ghidra.feature.vt.api.util.VTOptions;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.model.listing.*;
import ghidra.util.InvalidNameException;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
public abstract class GhidraVersionTrackingScript extends GhidraScript {
protected VTSession vtSession;
protected Program sourceProgram;
protected Program destinationProgram;
public abstract class AbstractGhidraVersionTrackingScript extends GhidraScript {
private VTSession vtSession;
private Program sourceProgram;
private Program destinationProgram;
private int transactionID;
public void createVersionTrackingSession(String sourceProgramPath,
String destinationProgramPath) throws Exception {
protected VTSession getVTSession() {
return vtSession;
}
protected Program getSourceProgram() {
return sourceProgram;
}
protected Program getDestinationProgram() {
return destinationProgram;
}
public VTSession createVersionTrackingSession(String sourceProgramPath,
String destinationProgramPath)
throws VersionException, CancelledException, IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to open a new session with one already open!");
}
sourceProgram = openProgram(sourceProgramPath);
destinationProgram = openProgram(destinationProgramPath);
createVersionTrackingSession("New Session", sourceProgram, destinationProgram);
try {
sourceProgram = openProgram(sourceProgramPath);
destinationProgram = openProgram(destinationProgramPath);
vtSession = new VTSessionDB("New Session", sourceProgram, destinationProgram, this);
transactionID = vtSession.startTransaction("VT Script");
}
finally {
if (vtSession == null) {
closeVersionTrackingSession();
}
}
return vtSession;
}
public void createVersionTrackingSession(String name, Program source, Program destination)
throws Exception {
public VTSession createVersionTrackingSession(String name, Program source, Program destination)
throws IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to create a new session with one already open!");
}
sourceProgram = source;
destinationProgram = destination;
if (!sourceProgram.isUsedBy(this)) {
try {
sourceProgram = source;
sourceProgram.addConsumer(this);
}
if (!destinationProgram.isUsedBy(this)) {
destinationProgram.addConsumer(this);
}
vtSession = VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
transactionID = vtSession.startTransaction("VT Script");
destinationProgram = destination;
destinationProgram.addConsumer(this);
vtSession = new VTSessionDB(name, sourceProgram, destinationProgram, this);
transactionID = vtSession.startTransaction("VT Script");
}
finally {
if (vtSession == null) {
closeVersionTrackingSession();
}
}
return vtSession;
}
public void openVersionTrackingSession(String path) throws Exception {
public VTSession openVersionTrackingSession(String path)
throws VersionException, CancelledException, IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to open a session with one already open!");
}
@ -79,51 +110,72 @@ public abstract class GhidraVersionTrackingScript extends GhidraScript {
DomainFile file = state.getProject().getProjectData().getFile(path);
vtSession = (VTSessionDB) file.getDomainObject(this, true, true, monitor);
sourceProgram = vtSession.getSourceProgram();
sourceProgram.addConsumer(this);
destinationProgram = vtSession.getDestinationProgram();
destinationProgram.addConsumer(this);
if (!sourceProgram.isUsedBy(this)) {
sourceProgram.addConsumer(this);
}
if (!destinationProgram.isUsedBy(this)) {
destinationProgram.addConsumer(this);
}
transactionID = vtSession.startTransaction("VT Script");
return vtSession;
}
public void saveVersionTrackingSession() throws IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to save a session when not open!");
}
vtSession.endTransaction(transactionID, true);
vtSession.save();
transactionID = vtSession.startTransaction("VT Script");
try {
vtSession.save();
}
finally {
transactionID = vtSession.startTransaction("VT Script");
}
}
public void saveSessionAs(String path, String name) throws Exception {
DomainFolder folder = state.getProject().getProjectData().getFolder(path);
folder.createFile(name, vtSession, monitor);
vtSession.setName(name);
public void saveSessionAs(String path, String name)
throws InvalidNameException, CancelledException, IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to save a session when not open!");
}
vtSession.endTransaction(transactionID, true);
try {
DomainFolder folder = state.getProject().getProjectData().getFolder(path);
folder.createFile(name, vtSession, monitor);
vtSession.setName(name);
}
finally {
transactionID = vtSession.startTransaction("VT Script");
}
}
@Override
public void cleanup(boolean success) {
closeVersionTrackingSession();
if (destinationProgram != null) {
closeProgram(destinationProgram);
}
if (sourceProgram != null) {
closeProgram(sourceProgram);
}
sourceProgram = null;
destinationProgram = null;
super.cleanup(success);
}
/**
* This will release the current session and both source and destination programs.
* If either program needs to be held it is the script's responsibility to first retain
* the instance and add itself as a consumer. Any program consumer must release it
* when done using it.
*/
public void closeVersionTrackingSession() {
if (vtSession != null) {
vtSession.endTransaction(transactionID, true);
vtSession.release(this);
vtSession = null;
}
if (destinationProgram != null) {
destinationProgram.release(this);
destinationProgram = null;
}
if (sourceProgram != null) {
sourceProgram.release(this);
sourceProgram = null;
}
}
public Program openProgram(String path)
private Program openProgram(String path)
throws VersionException, CancelledException, IOException {
if (state.getProject() == null) {
throw new RuntimeException("No project open.");
@ -132,11 +184,6 @@ public abstract class GhidraVersionTrackingScript extends GhidraScript {
return (Program) file.getDomainObject(this, true, true, monitor);
}
@Override
public void closeProgram(Program program) {
program.release(this);
}
public Set<String> getSourceFunctions() {
if (vtSession == null) {
throw new RuntimeException("You must have an open vt session");

View file

@ -13,33 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.feature.vt.api.impl;
package ghidra.feature.vt.api.db;
import java.io.IOException;
import javax.swing.Icon;
import db.DBHandle;
import db.OpenMode;
import db.buffers.BufferFile;
import generic.theme.GIcon;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.framework.data.DBContentHandler;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
private static Icon ICON = new GIcon("icon.version.tracking.session.content.type");
public static final String CONTENT_TYPE = "VersionTracking";
public final static String CONTENT_TYPE = "VersionTracking";
private static final Icon ICON = new GIcon("icon.version.tracking.session.content.type");
@Override
public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
@ -74,22 +71,40 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
return "Version Tracking";
}
private void checkContentAndExclusiveCheckout(FolderItem item) throws IOException {
String contentType = item.getContentType();
if (!contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType);
}
// NOTE: item.isVersioned indicates that item is located on versioned filesystem
// and is not checked-out, otheriwse assume item in local filesystem and must
// ensure if any checkout is exclusive.
if (item.isVersioned() || (item.isCheckedOut() && !item.isCheckedOutExclusive())) {
throw new IOException(
"Unsupported VT Session use: session file must be checked-out exclusive");
}
}
@Override
public VTSessionDB getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException {
String contentType = item.getContentType();
if (!contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType);
checkContentAndExclusiveCheckout(item);
if (item.isReadOnly()) {
throw new ReadOnlyException("VT Session file is set read-only which prevents its use");
}
try {
DatabaseItem dbItem = (DatabaseItem) item;
BufferFile bf = dbItem.openForUpdate(checkoutId);
DBHandle dbh = new DBHandle(bf, okToRecover, monitor);
boolean success = false;
try {
VTSessionDB db = VTSessionDB.getVTSession(dbh, OpenMode.UPGRADE, consumer, monitor);
// NOTE: Always open with DB upgrade enabled
VTSessionDB db = new VTSessionDB(dbh, monitor, consumer);
success = true;
return db;
}
@ -99,13 +114,7 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
}
}
}
catch (VersionException e) {
throw e;
}
catch (IOException e) {
throw e;
}
catch (CancelledException e) {
catch (VersionException | IOException | CancelledException e) {
throw e;
}
catch (Throwable t) {
@ -134,12 +143,7 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
int minChangeVersion, TaskMonitor monitor)
throws IOException, CancelledException, VersionException {
String contentType = item.getContentType();
if (!contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType);
}
return getReadOnlyObject(item, -1, false, consumer, monitor);
}
@Override
@ -154,43 +158,14 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
Object consumer, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
String contentType = item.getContentType();
if (contentType != null && !contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType);
}
try {
DatabaseItem dbItem = (DatabaseItem) item;
BufferFile bf = dbItem.open();
DBHandle dbh = new DBHandle(bf);
boolean success = false;
try {
VTSessionDB manager =
VTSessionDB.getVTSession(dbh, OpenMode.READ_ONLY, consumer, monitor);
success = true;
return manager;
}
finally {
if (!success) {
dbh.close();
}
}
}
catch (IOException e) {
throw e;
}
catch (Throwable t) {
Msg.error(this, "Get read-only object failed", t);
String msg = t.getMessage();
if (msg == null) {
msg = t.toString();
}
throw new IOException("Open failed: " + msg, t);
}
checkContentAndExclusiveCheckout(item);
throw new ReadOnlyException("VT Session does not support read-only use");
}
@Override
public boolean isPrivateContentType() {
return true;
return false;
}
}

View file

@ -26,6 +26,7 @@ import ghidra.feature.vt.api.correlator.program.ImpliedMatchProgramCorrelator;
import ghidra.feature.vt.api.correlator.program.ManualMatchProgramCorrelator;
import ghidra.feature.vt.api.impl.*;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status;
@ -36,26 +37,30 @@ import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE };
private final static String[] COL_TYPES = new String[] { "Value" };
private final static Schema SCHEMA =
new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES);
private static final String PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs";
private static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID";
private static final String DESTINATION_PROGRAM_ID_PROPERTY_KEY = "DestinationProgramID";
// Source and Destination Program IDs are retained within OptionsDB
static final String PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs";
static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID";
static final String DESTINATION_PROGRAM_ID_PROPERTY_KEY = "DestinationProgramID";
private static final String UNUSED_DEFAULT_NAME = "Untitled";
private static final int EVENT_NOTIFICATION_DELAY = 500;
private static final int EVENT_BUFFER_SIZE = 100;
private static final long MANUAL_MATCH_SET_ID = 0;
private static final long IMPLIED_MATCH_SET_ID = -1;
// PropertyTable is used solely to retain DB version
// NOTE: OptionsDB already has a table named "Property Table"
private static final String PROPERTY_TABLE_NAME = "PropertyTable";
private static final String DB_VERSION_PROPERTY_NAME = "DB_VERSION";
@ -65,8 +70,11 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
* 14-Nov-2019 - version 2 - Corrected fixed length indexing implementation causing
* change in index table low-level storage for newly
* created tables.
* 16-Feb-2024 - version 3 - No schema change. Version imposed to prevent older versions
* of Ghidra from opening session objects which may have been
* added to version controlled repository.
*/
private static final int DB_VERSION = 2;
private static final int DB_VERSION = 3;
/**
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION any time the
@ -75,7 +83,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
* if the data's version is >= UPGRADE_REQUIRED_BEFORE_VERSION and <= DB_VERSION.
*/
// NOTE: Schema upgrades are not currently supported
private static final int UPGRADE_REQUIRED_BEFORE_VERSION = 1;
private static final int UPGRADE_REQUIRED_BEFORE_VERSION = 3;
private VTMatchSetTableDBAdapter matchSetTableAdapter;
private AssociationDatabaseManager associationManager;
@ -89,40 +97,119 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
private VTMatchSet impliedMatchSet;
private boolean changeSetsModified = false;
private Table propertyTable;
private Table propertyTable; // used to retain DB version only
/**
* Factory method which constructs a new VTSessionDB using specified source and desitination
* programs.
* @param name name to be assigned to the resulting domain object file
* @param sourceProgram session source program within active project
* @param destinationProgram session destination program open for update within active project
* @param consumer object consumer resposible for the proper release of the returned instance.
* @return new {@link VTSessionDB} object
* @throws IOException if an IO error occurs
* @deprecated {@link #VTSessionDB(String, Program, Program, Object)} should be used instead
*/
@Deprecated(since = "11.1", forRemoval = true)
public static VTSessionDB createVTSession(String name, Program sourceProgram,
Program destinationProgram, Object consumer) throws IOException {
return new VTSessionDB(name, sourceProgram, destinationProgram, consumer);
}
VTSessionDB session = new VTSessionDB(new DBHandle(), consumer);
/**
* Construct a new VTSessionDB using specified source and desitination programs.
* @param name name to be assigned to the resulting domain object file
* @param sourceProgram session source program within active project
* @param destinationProgram session destination program open for update within active project
* @param consumer object consumer resposible for the proper release of the returned instance.
* @throws IOException if an IO error occurs
*/
public VTSessionDB(String name, Program sourceProgram, Program destinationProgram,
Object consumer) throws IOException {
super(new DBHandle(), UNUSED_DEFAULT_NAME, EVENT_NOTIFICATION_DELAY, consumer);
int ID = session.startTransaction("Constructing New Version Tracking Match Set");
propertyTable = dbh.getTable(PROPERTY_TABLE_NAME);
int ID = startTransaction("Constructing New Version Tracking Match Set");
try {
session.propertyTable = session.dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
session.matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(session.dbh);
session.associationManager =
AssociationDatabaseManager.createAssociationManager(session.dbh, session);
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.dbh);
session.initializePrograms(sourceProgram, destinationProgram);
session.createMatchSet(
new ManualMatchProgramCorrelator(sourceProgram, destinationProgram),
propertyTable = dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(dbh);
associationManager = AssociationDatabaseManager.createAssociationManager(dbh, this);
matchTagAdapter = VTMatchTagDBAdapter.createAdapter(dbh);
initializePrograms(sourceProgram, destinationProgram, true);
createMatchSet(new ManualMatchProgramCorrelator(sourceProgram, destinationProgram),
MANUAL_MATCH_SET_ID);
session.createMatchSet(
new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
createMatchSet(new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
IMPLIED_MATCH_SET_ID);
session.updateVersion();
updateVersion();
}
finally {
session.endTransaction(ID, true);
endTransaction(ID, true);
}
try {
session.addSynchronizedDomainObject(destinationProgram);
addSynchronizedDomainObject(destinationProgram);
}
catch (Exception e) {
session.close();
throw new RuntimeException(e.getMessage(), e);
close();
throw new RuntimeException(e);
}
return session;
}
/**
* Construct an existing VT session object and open with UPGRADE enabled.
* The caller (i.e., content handler) must ensure that project has exclusive access to
* the domain file before it was open and {@link DBHandle} supplied.
* @param dbHandle database handle
* @param monitor TaskMonitor that allows the open to be canceled.
* @param consumer the object that keeping the session open.
* @throws IOException if an error accessing the database occurs.
* @throws VersionException if database version does not match implementation, UPGRADE may be possible.
* @throws CancelledException if instantiation is canceled by monitor
*/
@SuppressWarnings("unused")
VTSessionDB(DBHandle dbHandle, TaskMonitor monitor, Object consumer)
throws VersionException, IOException, CancelledException {
super(dbHandle, UNUSED_DEFAULT_NAME, EVENT_NOTIFICATION_DELAY, consumer);
// openMode forced to UPGRADE since we do not support read-only mode
// It is assumed we always have exclusive access to the underlying database
OpenMode openMode = OpenMode.UPGRADE;
propertyTable = dbHandle.getTable(PROPERTY_TABLE_NAME);
int storedVersion = getVersion();
if (storedVersion > DB_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
// The following version logic holds true for DB_VERSION <= 3 which assume no additional
// DB index tables will be added when open for update/upgrade. This may not hold
// true for future revisions associated with table schema changes in which case the
// UPGRADE_REQUIRED_BEFORE_VERSION value should equal DB_VERSION. Current logic
// assumes no schema changes will be made during upgrade.
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) {
if (openMode != OpenMode.UPGRADE) { // should always be open with UPGRADE mode
throw new VersionException(
"Version Tracking Sessions do not support schema upgrades.",
VersionException.OLDER_VERSION, true);
}
withTransaction("Update DBVersion", () -> updateVersion());
clearUndo(false);
changed = true;
}
// NOTE: code below will not make changes (no transaction is open)
// Additional supported required to facilitate schema change during upgrade if needed.
matchSetTableAdapter = VTMatchSetTableDBAdapter.getAdapter(dbHandle, openMode, monitor);
associationManager =
AssociationDatabaseManager.getAssociationManager(dbHandle, this, openMode, monitor);
matchTagAdapter = VTMatchTagDBAdapter.getAdapter(dbHandle, openMode, monitor);
loadMatchSets(openMode, monitor);
}
private void updateVersion() throws IOException {
@ -131,47 +218,174 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
propertyTable.putRecord(record);
}
public static VTSessionDB getVTSession(DBHandle dbHandle, OpenMode openMode, Object consumer,
TaskMonitor monitor) throws VersionException, IOException {
VTSessionDB session = new VTSessionDB(dbHandle, consumer);
int storedVersion = session.getVersion();
if (storedVersion > DB_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
private int getVersion() throws IOException {
// DB Version was added in release (11/6/2012)
// if record does not exist return 0;
if (propertyTable == null) {
return 0;
}
// The following version logic holds true for DB_VERSION=2 which assumes no additional
// DB index tables will be added when open for update/upgrade. This will not hold
// true for future revisions associated with table schema changes in which case the
// UPGRADE_REQUIRED_BEFORE_VERSION value should equal DB_VERSION.
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) {
throw new VersionException("Version Tracking Sessions do not support schema upgrades.");
DBRecord record = propertyTable.getRecord(new StringField(DB_VERSION_PROPERTY_NAME));
if (record != null) {
String s = record.getString(0);
try {
return Integer.parseInt(s);
}
catch (NumberFormatException e) {
// just use default
}
}
return 0;
}
@Override
protected void setDomainFile(DomainFile df) throws DomainObjectException {
DomainFolder parent = df.getParent();
if (parent != null && sourceProgram == null) {
try {
openSourceAndDestinationPrograms(parent.getProjectData());
}
catch (IOException e) {
throw new DomainObjectException(e);
}
}
super.setDomainFile(df);
}
/**
* Open associated source and destination program files and complete session initialization.
* @param projectData active project data
* @throws IOException if source or destination program not found within specified project
* or an error occured while opening them (e.g., upgrade required).
*/
private void openSourceAndDestinationPrograms(ProjectData projectData) throws IOException {
String sourceProgramID = getSourceProgramID();
String destinationProgramID = getDestinationProgramID();
DomainFile sourceFile = projectData.getFileByID(sourceProgramID);
DomainFile destinationFile = projectData.getFileByID(destinationProgramID);
if (sourceFile == null) {
throw new IOException("Source program is missing for this Version Tracking Session!");
}
if (destinationFile == null) {
throw new IOException(
"Destination program is missing for this Version Tracking Session!");
}
session.matchSetTableAdapter =
VTMatchSetTableDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor);
session.associationManager =
AssociationDatabaseManager.getAssociationManager(dbHandle, session, openMode, monitor);
session.matchTagAdapter =
VTMatchTagDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor);
session.loadMatchSets(openMode, monitor);
return session;
// Must ensure that destination program file can be updated
VTSessionFileUtil.validateDestinationProgramFile(destinationFile, true,
SystemUtilities.isInHeadlessMode());
VTSessionFileUtil.validateSourceProgramFile(sourceFile, true);
sourceProgram = openProgram(sourceFile, true);
if (sourceProgram != null) {
destinationProgram = openProgram(destinationFile, false);
}
if (sourceProgram == null || destinationProgram == null) {
StringBuilder buffer = new StringBuilder(
"Session not opened because one or both programs did not open.\n");
if (sourceProgram != null) {
sourceProgram.release(this);
sourceProgram = null;
}
else {
buffer.append("\tUnable to open source program \"" + sourceFile + "\"\n");
}
if (destinationProgram != null) {
destinationProgram.release(this);
destinationProgram = null;
}
else {
buffer.append("\tUnable to open destination program \"" + destinationFile + "\"\n");
}
throw new IOException(buffer.toString());
}
associationManager.sessionInitialized();
try {
addSynchronizedDomainObject(destinationProgram);
}
catch (Exception e) {
sourceProgram.release(this);
sourceProgram = null;
destinationProgram.release(this);
destinationProgram = null;
throw new IOException(e.getMessage());
}
}
private Program openProgram(DomainFile domainFile, boolean isSource) {
String type = isSource ? "VT Source Program" : "VT Destination Program";
if (SystemUtilities.isInHeadlessMode()) {
try {
return (Program) domainFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e); // unexpected
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), type, "open",
e);
}
catch (IOException e) {
Msg.showError(this, null, "Can't open " + type + ": " + domainFile.getName(),
e.getMessage());
}
return null;
}
// Headed GUI Mode
OpenProgramTask openTask = new OpenProgramTask(domainFile, this);
openTask.setOpenPromptText("Open " + type);
TaskLauncher.launch(openTask);
OpenProgramRequest openProgram = openTask.getOpenProgram();
return openProgram != null ? openProgram.getProgram() : null;
}
public String getSourceProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(SOURCE_PROGRAM_ID_PROPERTY_KEY, "");
}
public String getDestinationProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(DESTINATION_PROGRAM_ID_PROPERTY_KEY, "");
}
@SuppressWarnings("hiding")
// this is from our constructor
private void initializePrograms(Program sourceProgram, Program destinationProgram) {
this.sourceProgram = sourceProgram;
this.destinationProgram = destinationProgram;
sourceProgram.addConsumer(this);
destinationProgram.addConsumer(this);
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
DomainFile sourceDomainFile = sourceProgram.getDomainFile();
properties.setString(SOURCE_PROGRAM_ID_PROPERTY_KEY, sourceDomainFile.getFileID());
DomainFile destinationDomainFile = destinationProgram.getDomainFile();
properties.setString(DESTINATION_PROGRAM_ID_PROPERTY_KEY,
destinationDomainFile.getFileID());
private void initializePrograms(Program sourceProgram, Program destinationProgram,
boolean rememberProgramIds) throws IOException {
if (!destinationProgram.canSave()) {
throw new ReadOnlyException(
"VT Session destination program is read-only which prevents its use");
}
this.sourceProgram = sourceProgram;
sourceProgram.addConsumer(this);
this.destinationProgram = destinationProgram;
destinationProgram.addConsumer(this);
if (rememberProgramIds) {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
DomainFile sourceDomainFile = sourceProgram.getDomainFile();
properties.setString(SOURCE_PROGRAM_ID_PROPERTY_KEY, sourceDomainFile.getFileID());
DomainFile destinationDomainFile = destinationProgram.getDomainFile();
properties.setString(DESTINATION_PROGRAM_ID_PROPERTY_KEY,
destinationDomainFile.getFileID());
}
}
@Override
@ -202,119 +416,6 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
sourceProgram.addConsumer(this);
}
public String getSourceProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(SOURCE_PROGRAM_ID_PROPERTY_KEY, "");
}
public String getDestinationProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(DESTINATION_PROGRAM_ID_PROPERTY_KEY, "");
}
private VTSessionDB(DBHandle dbHandle, Object consumer) {
super(dbHandle, UNUSED_DEFAULT_NAME, EVENT_NOTIFICATION_DELAY, consumer);
propertyTable = dbHandle.getTable(PROPERTY_TABLE_NAME);
}
public int getVersion() throws IOException {
// DB Version was added in release (11/6/2012)
// if record does not exist return 0;
if (propertyTable == null) {
return 0;
}
DBRecord record = propertyTable.getRecord(new StringField(DB_VERSION_PROPERTY_NAME));
if (record != null) {
String s = record.getString(0);
try {
return Integer.parseInt(s);
}
catch (NumberFormatException e) {
// just use default
}
}
return 0;
}
@Override
protected void setDomainFile(DomainFile df) {
super.setDomainFile(df);
DomainFolder parent = df.getParent();
if (parent == null) {
return;
}
if (sourceProgram != null) { // source and destination are already open
return;
}
ProjectData projectData = parent.getProjectData();
String sourceProgramID = getSourceProgramID();
String destinationProgramID = getDestinationProgramID();
DomainFile sourceFile = projectData.getFileByID(sourceProgramID);
DomainFile destinationFile = projectData.getFileByID(destinationProgramID);
if (sourceFile == null) {
throw new RuntimeException(
"Source program is missing for this Version Tracking Session!");
}
if (destinationFile == null) {
throw new RuntimeException(
"Destination program is missing for this Version Tracking Session!");
}
sourceProgram = openProgram(sourceFile, true);
if (sourceProgram != null) {
destinationProgram = openProgram(destinationFile, false);
}
if (sourceProgram == null || destinationProgram == null) {
StringBuilder buffer = new StringBuilder(
"Session not opened because one or both programs did not open.\n");
if (sourceProgram != null) {
sourceProgram.release(this);
sourceProgram = null;
}
else {
buffer.append("\tUnable to open source program \"" + sourceFile + "\"\n");
}
if (destinationProgram != null) {
destinationProgram.release(this);
destinationProgram = null;
}
else {
buffer.append("\tUnable to open destination program \"" + destinationFile + "\"\n");
}
throw new RuntimeException(buffer.toString());
}
associationManager.sessionInitialized();
try {
addSynchronizedDomainObject(destinationProgram);
}
catch (Exception e) {
sourceProgram.release(this);
destinationProgram.release(this);
throw new RuntimeException(e.getMessage());
}
}
private Program openProgram(DomainFile domainFile, boolean isSource) {
OpenProgramTask openTask = new OpenProgramTask(domainFile, this);
String type = isSource ? "(source program)" : "(destination program)";
openTask.setOpenPromptText("Open " + type);
TaskLauncher.launch(openTask);
OpenProgramRequest openProgram = openTask.getOpenProgram();
return openProgram != null ? openProgram.getProgram() : null;
}
@Override
public void release(Object consumer) {
super.release(consumer);

View file

@ -0,0 +1,186 @@
/* ###
* 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.feature.vt.api.util;
import java.io.IOException;
import ghidra.app.util.dialog.CheckoutDialog;
import ghidra.app.util.task.ProgramOpener;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.remote.User;
import ghidra.program.database.ProgramDB;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
/**
* {@link VTSessionFileUtil} provides methods for checking {@link VTSessionDB} source and
* destination program files prior to being opened and used during session instantiation.
*/
public class VTSessionFileUtil {
// static utility class
private VTSessionFileUtil() {
}
/**
* Validate a VT source program to ensure it meets minimum criteria to open with a VTSession.
* The following validation checks are performed:
* <ul>
* <li>file must correspond to a ProgramDB</li>
* </ul>
* If an error is thrown it is intended to be augmented for proper presentation.
*
* @param file VT Session source program domain file
* @param includeFilePathInError if true file path will be appended to any exception throw
* @throws IllegalArgumentException if any VT source program file criteria is not satisfied
*/
public static void validateSourceProgramFile(DomainFile file, boolean includeFilePathInError)
throws IllegalArgumentException {
String error = null;
if (!ProgramDB.class.isAssignableFrom(file.getDomainObjectClass())) {
error = "Source file does not correspond to a Program";
}
if (error != null) {
if (includeFilePathInError) {
error += ":\n" + file.getPathname();
}
throw new IllegalArgumentException(error);
}
}
/**
* Validate a VT destination program to ensure it meets minimum criteria to open with a VTSession.
* GUI mode only: If file is versioned and not checked-out the user may be prompted to perform
* an optional checkout of the file. Prompting for checkout will not occur if this method
* is invoked from the Swing thread or operating in a headless mode.
* The following validation checks are performed:
* <ul>
* <li>file must correspond to a ProgramDB</li>
* <li>file must be contained within the active project</li>
* <li>file must not be marked read-only</li>
* <li>if file is versioned it must be checked-out (user may be prompted to do this)</li>
* </ul>
* If an error is thrown it is intended to be augmented for proper presentation.
*
* @param file VT Session destination program domain file
* @param includeFilePathInError if true file path will be appended to any exception throw
* @param silent if user interaction should not be performed. This should be true if
* filesystem lock is currently held.
* @throws IllegalArgumentException if any VT destination program file criteria is not satisfied
*/
public static void validateDestinationProgramFile(DomainFile file,
boolean includeFilePathInError, boolean silent) throws IllegalArgumentException {
String error = null;
if (!ProgramDB.class.isAssignableFrom(file.getDomainObjectClass())) {
error = "Destination file does not correspond to a Program";
}
else {
DomainFolder folder = file.getParent();
if (folder == null || !folder.isInWritableProject()) {
error = "Destination file must be from active project";
}
else if (file.isReadOnly()) {
error = "Destination file must not be read-only";
}
else if (file.isVersioned()) {
if (!silent) {
doOptionalDestinationProgramCheckout(file);
}
if (!file.isCheckedOut()) {
error = "Versioned destination file must be checked-out for update";
}
}
}
if (error != null) {
if (includeFilePathInError) {
error += ":\n" + file.getPathname();
}
throw new IllegalArgumentException(error);
}
}
/**
* Determine if the specified {@link DomainFile} will permit update.
* @param file domain file
* @return true if file permits update else false
*/
public static boolean canUpdate(DomainFile file) {
DomainFolder folder = file.getParent();
if (folder == null || !folder.isInWritableProject()) {
return false;
}
if (file.isReadOnly()) {
return false;
}
if (file.isVersioned()) {
return false;
}
return true;
}
private static void doOptionalDestinationProgramCheckout(DomainFile file) {
if (SystemUtilities.isInHeadlessMode() || !file.canCheckout()) {
return;
}
User user = file.getParent().getProjectData().getUser();
CheckoutDialog dialog = new CheckoutDialog(file, user);
dialog.setTitle("VT Destination Program not Checked Out");
if (dialog.showDialog() == CheckoutDialog.CHECKOUT) { // uses Swing thread
CheckoutDestinationProgramTask task =
new CheckoutDestinationProgramTask(file, dialog.exclusiveCheckout());
TaskLauncher.launch(task);
}
}
private static class CheckoutDestinationProgramTask extends Task {
private DomainFile file;
boolean exclusiveCheckout;
CheckoutDestinationProgramTask(DomainFile file, boolean exclusiveCheckout) {
super("Checking Out " + file, true, true, true, true);
this.file = file;
this.exclusiveCheckout = exclusiveCheckout;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Checking Out " + file);
try {
if (!file.checkout(exclusiveCheckout, monitor)) {
Msg.showError(ProgramOpener.class, null, "Checkout Failed",
"Exclusive checkout failed for: " + file +
"\nOne or more users have file checked out!");
}
}
catch (IOException e) {
Msg.showError(ProgramOpener.class, null, "Checkout Failed",
"Checkout failed for: " + file + "\n" + e.getMessage());
}
catch (CancelledException e) {
// ignore
}
}
}
}

View file

@ -46,7 +46,7 @@ public interface VTController extends VTSessionSupplier {
@Override
public VTSession getSession();
public void openVersionTrackingSession(DomainFile domainFile);
public boolean openVersionTrackingSession(DomainFile domainFile);
public void openVersionTrackingSession(VTSession session);

View file

@ -22,31 +22,36 @@ import java.util.*;
import javax.swing.SwingUtilities;
import docking.ActionContext;
import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.feature.vt.api.db.VTAssociationDB;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.feature.vt.gui.duallisting.VTListingContext;
import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemContext;
import ghidra.feature.vt.gui.task.SaveTask;
import ghidra.feature.vt.gui.task.VtTask;
import ghidra.feature.vt.gui.util.MatchInfo;
import ghidra.feature.vt.gui.util.MatchInfoFactory;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.SaveDataDialog;
import ghidra.framework.main.projectdata.actions.CheckoutsDialog;
import ghidra.framework.model.*;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.store.ItemCheckoutStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.AddressCorrelation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.*;
import ghidra.util.datastruct.WeakValueHashMap;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
@ -94,30 +99,193 @@ public class VTControllerImpl
return session;
}
private boolean checkSessionFileAccess(DomainFile domainFile) {
DomainFolder folder = domainFile.getParent();
if (folder == null || !folder.isInWritableProject()) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file use limited to active project only.");
return false;
}
if (domainFile.isVersioned()) {
if (domainFile.isCheckedOut()) {
if (!domainFile.isCheckedOutExclusive()) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file is checked-out but does not have exclusive access.\n" +
"You must undo checkout and re-checkout with exclusive access.");
return false;
}
if (domainFile.isReadOnly()) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file is set read-only which prevents its use.");
return false;
}
return true;
}
return checkoutSession(domainFile);
}
else if (domainFile.isReadOnly()) { // non-versioned file
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file is set read-only which prevents its use.");
return false;
}
return true;
}
private boolean checkoutSession(DomainFile domainFile) {
Project activeProject = AppInfo.getActiveProject();
RepositoryAdapter repository = activeProject.getRepository();
if (repository != null) {
try {
ItemCheckoutStatus[] checkouts = domainFile.getCheckouts();
if (checkouts.length != 0) {
int rc = OptionDialog.showOptionDialogWithCancelAsDefaultButton(null,
"Checkout VT Session",
"VT Session " + domainFile.getName() + " is NOT CHECKED OUT but " +
"is checked-out by another user.\n" +
"Opening VT Session requires an exclusive check out of this file.\n" +
"Do you want to view the list of active checkouts for this file?",
"View Checkout(s)...");
if (rc != OptionDialog.OPTION_ONE) {
return false;
}
CheckoutsDialog dialog = new CheckoutsDialog(plugin.getTool(),
repository.getUser(), domainFile, checkouts);
plugin.getTool().showDialog(dialog);
return false;
}
}
catch (IOException e) {
Msg.showError(this, null, "Checkout VT Session Failed: " + domainFile.getName(),
e.getMessage());
return false;
}
}
int rc = OptionDialog.showOptionDialogWithCancelAsDefaultButton(null, "Checkout VT Session",
"VT Session " + domainFile.getName() + " is NOT CHECKED OUT.\n" +
"Opening VT Session requires an exclusive check out of this file.\n" +
"Do you want to Check Out this file?",
"Checkout...");
if (rc != OptionDialog.OPTION_ONE) {
return false;
}
TaskLauncher.launchModal("Checkout VT Session", new MonitoredRunnable() {
@Override
public void monitoredRun(TaskMonitor monitor) {
try {
domainFile.checkout(true, monitor);
}
catch (CancelledException e) {
// ignore
}
catch (IOException e) {
Msg.showError(this, null, "Checkout VT Session Failed: " + domainFile.getName(),
e.getMessage());
}
}
});
return domainFile.isCheckedOutExclusive();
}
@Override
public void openVersionTrackingSession(DomainFile domainFile) {
public boolean openVersionTrackingSession(DomainFile domainFile) {
if (!VTSession.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
throw new IllegalArgumentException("File does not correspond to a VTSession");
}
if (!checkForUnSavedChanges()) {
return;
return false;
}
try {
VTSessionDB newSession =
(VTSessionDB) domainFile.getDomainObject(this, true, true, TaskMonitor.DUMMY);
doOpenSession(newSession);
}
catch (VersionException e) {
Msg.showError(this, null, "Can't open domainFile " + domainFile.getName(),
e.getMessage());
if (!checkSessionFileAccess(domainFile)) {
return false;
}
VTSessionDB vtSessionDB = getVTSessionDB(domainFile, this);
if (vtSessionDB != null) {
try {
openVersionTrackingSession(vtSessionDB);
return true;
}
finally {
vtSessionDB.release(this);
}
}
}
catch (CancelledException e) {
Msg.error(this, "Got unexexped cancelled exception", e);
// ignore - return false
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), "VT Session",
"open", e);
}
catch (IOException e) {
Msg.showError(this, null, "Can't open " + domainFile.getName(), e.getMessage());
Msg.showError(this, null, "Can't open VT Session: " + domainFile.getName(),
e.getMessage());
}
return false;
}
private static class OpenVTSessionTask extends Task {
private final Object consumer;
private final DomainFile vtSessionFile;
Exception exception;
VTSessionDB vtSessionDB;
OpenVTSessionTask(DomainFile vtSessionFile, Object consumer) {
super("Opening VT Session", true, false, true, true);
this.vtSessionFile = vtSessionFile;
this.consumer = consumer;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
vtSessionDB =
(VTSessionDB) vtSessionFile.getDomainObject(consumer, true, true, monitor);
}
catch (Exception e) {
exception = e;
}
}
}
private VTSessionDB getVTSessionDB(DomainFile vtSessionFile, Object consumer)
throws IOException, VersionException, CancelledException {
OpenVTSessionTask task = new OpenVTSessionTask(vtSessionFile, consumer);
TaskLauncher.launch(task);
if (task.exception != null) {
if (task.exception instanceof CancelledException ce) {
throw ce;
}
if (task.exception instanceof VersionException ve) {
throw ve;
}
if (task.exception instanceof IOException ioe) {
throw ioe;
}
throw new IOException("VTSessionDB failure", task.exception);
}
return task.vtSessionDB;
}
@Override
public void openVersionTrackingSession(VTSession newSession) {
// FIXME: new session wizard should have handled existing session before starting -
// should be no need for this check
if (!checkForUnSavedChanges()) {
return;
}
@ -595,43 +763,79 @@ public class VTControllerImpl
// Inner Classes
//==================================================================================================
private void updateProgram(DomainFile file, boolean isSource) {
String type = isSource ? "Source" : "Destination";
Program newProgram;
try {
newProgram = (Program) file.getDomainObject(this, false, false, TaskMonitor.DUMMY);
}
catch (Exception e) {
Msg.showError(this, getParentComponent(),
"Error opening VT " + type + " Program: " + file, e);
return;
}
if (isSource) {
session.updateSourceProgram(newProgram);
}
else {
session.updateDestinationProgram(newProgram);
}
// List<DomainObjectChangeRecord> events = new ArrayList<DomainObjectChangeRecord>();
// events.add(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
// domainObjectChanged(new DomainObjectChangedEvent(newProgram, events));
matchInfoFactory.clearCache();
fireSessionChanged();
}
private class MyFolderListener extends DomainFolderListenerAdapter {
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
/**
* Special handling for when a file is checked-in. The existing program has be moved
* to a proxy file (no longer in the project) so that it can be closed and the program
* re-opened with the new version after the check-in merge.
*/
if (session == null) {
return;
}
if (session.getSourceProgram() != oldObject &&
session.getDestinationProgram() != oldObject) {
return;
}
Program newProgram;
try {
newProgram = (Program) file.getDomainObject(this, false, false, TaskMonitor.DUMMY);
}
catch (Exception e) {
Msg.showError(this, getParentComponent(), "Error opening program " + file, e);
if (session.getSourceProgram() == oldObject) {
updateProgram(file, true);
return;
}
if (oldObject == session.getSourceProgram()) {
session.updateSourceProgram(newProgram);
String type;
if (session == oldObject) {
type = "VT Session";
}
else if (oldObject == session.getDestinationProgram()) {
session.updateDestinationProgram(newProgram);
else if (session.getDestinationProgram() == oldObject) {
if (VTSessionFileUtil.canUpdate(file)) {
updateProgram(file, false);
return;
}
type = "Destination Program";
}
// List<DomainObjectChangeRecord> events = new ArrayList<DomainObjectChangeRecord>();
// events.add(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
// domainObjectChanged(new DomainObjectChangedEvent(newProgram, events));
matchInfoFactory.clearCache();
fireSessionChanged();
else {
return;
}
// Session or destination program can no longer be saved to project so we
// have no choice but to close session.
// Since we are already in the Swing thread we need to delay closing so we do
// not continue to block the Swing thread and the checkin which is in progress.
// This allows the DomainFile checkin to complete its processing first.
SwingUtilities.invokeLater(() -> {
Msg.showInfo(this, plugin.getTool().getToolFrame(), "Closing VT Session",
type + " checkin has forced session close.\n" +
"You will be prompted to save any other changes if needed, after which\n" +
"you may reopen the VT Session.");
closeVersionTrackingSession();
// NOTE: a future convenience could be added to attempt reopening of session
});
}
}

View file

@ -216,10 +216,10 @@ public class VTPlugin extends Plugin {
for (DomainFile domainFile : data) {
if (domainFile != null &&
VTSession.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
openVersionTrackingSession(domainFile);
return true;
return controller.openVersionTrackingSession(domainFile);
}
}
DomainFile programFile1 = null;
DomainFile programFile2 = null;
for (DomainFile domainFile : data) {
@ -249,10 +249,6 @@ public class VTPlugin extends Plugin {
return false;
}
private void openVersionTrackingSession(DomainFile domainFile) {
controller.openVersionTrackingSession(domainFile);
}
@Override
public void readConfigState(SaveState saveState) {
controller.readConfigState(saveState);
@ -274,20 +270,18 @@ public class VTPlugin extends Plugin {
@Override
public void readDataState(SaveState saveState) {
String pathname = saveState.getString("PATHNAME", null);
String location = saveState.getString("PROJECT_LOCATION", null);
String projectName = saveState.getString("PROJECT_NAME", null);
if (location == null || projectName == null) {
if (pathname == null) {
return;
}
ProjectLocator url = new ProjectLocator(location, projectName);
ProjectData projectData = tool.getProject().getProjectData(url);
if (projectData == null) {
Msg.showError(this, tool.getToolFrame(), "File Not Found", "Could not find " + url);
Project project = tool.getProject();
if (project == null) {
return;
}
ProjectData projectData = project.getProjectData();
DomainFile domainFile = projectData.getFile(pathname);
if (domainFile == null) {
return;
}
controller.openVersionTrackingSession(domainFile);
}
@ -298,21 +292,7 @@ public class VTPlugin extends Plugin {
return;
}
DomainFile domainFile = session.getDomainFile();
String projectLocation = null;
String projectName = null;
String path = null;
ProjectLocator url = domainFile.getProjectLocator();
if (url != null) {
projectLocation = url.getLocation();
projectName = url.getName();
path = domainFile.getPathname();
}
saveState.putString("PROJECT_LOCATION", projectLocation);
saveState.putString("PROJECT_NAME", projectName);
saveState.putString("PATHNAME", path);
saveState.putString("PATHNAME", domainFile.getPathname());
}
@Override

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +15,11 @@
*/
package ghidra.feature.vt.gui.wizard;
import java.io.IOException;
import docking.wizard.WizardState;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.DomainFolder;
import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException;
@ -28,11 +28,6 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
import java.awt.EventQueue;
import java.io.IOException;
import docking.wizard.WizardState;
public class CreateNewSessionTask extends Task {
private final WizardState<VTWizardStateKey> state;
private final VTController controller;
@ -45,57 +40,41 @@ public class CreateNewSessionTask extends Task {
@Override
public void run(TaskMonitor monitor) {
VTSession session = null;
VTSessionDB session = null;
String name = null;
try {
Program sourceProgram = (Program) state.get(VTWizardStateKey.SOURCE_PROGRAM);
Program destinationProgram = (Program) state.get(VTWizardStateKey.DESTINATION_PROGRAM);
session =
VTSessionDB.createVTSession("New Session", sourceProgram, destinationProgram, this);
session = new VTSessionDB("New Session", sourceProgram, destinationProgram, this);
DomainObjectAdapterDB dobj = null;
if (session instanceof DomainObjectAdapterDB) {
dobj = (DomainObjectAdapterDB) session;
}
sourceProgram.release(controller.getTool());
destinationProgram.release(controller.getTool());
if (dobj != null) {
name = (String) state.get(VTWizardStateKey.SESSION_NAME);
DomainFolder folder = (DomainFolder) state.get(VTWizardStateKey.NEW_SESSION_FOLDER);
try {
folder.createFile(name, dobj, monitor);
}
catch (InvalidNameException e) {
Msg.showError(this, null, "Invalid Domain Object Name",
"Please report this error; the name should have been checked already");
}
name = (String) state.get(VTWizardStateKey.SESSION_NAME);
DomainFolder folder = (DomainFolder) state.get(VTWizardStateKey.NEW_SESSION_FOLDER);
try {
folder.createFile(name, session, monitor);
}
catch (InvalidNameException e) {
Msg.showError(this, null, "Invalid Domain Object Name",
"Please report this error; the name should have been checked already");
}
final VTSession finalSession = session;
EventQueue.invokeLater(new Runnable() {
public void run() {
controller.openVersionTrackingSession(finalSession);
releaseDomainObject(finalSession);
}
});
controller.openVersionTrackingSession(session);
}
catch (CancelledException e) {
// the user cancelled; just cleanup
releaseDomainObject(session);
// ignore
}
catch (IOException e) {
releaseDomainObject(session);
Msg.showError(this, null, "Failed to Create Session", "Failed to create db file: " +
name, e);
Msg.showError(this, null, "Failed to Create Session",
"Failed to create db file: " + name, e);
}
finally {
if (session != null) {
session.release(this);
}
}
}
private void releaseDomainObject(VTSession session) {
if (session == null) {
return;
}
((VTSessionDB) session).release(this);
}
}

View file

@ -15,22 +15,10 @@
*/
package ghidra.feature.vt.gui.wizard;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.awt.*;
import java.util.*;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
@ -38,22 +26,19 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.button.BrowseButton;
import docking.widgets.label.GDLabel;
import docking.wizard.AbstractMageJPanel;
import docking.wizard.WizardPanelDisplayability;
import docking.wizard.WizardState;
import docking.wizard.*;
import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Ids.Fonts;
import generic.theme.Gui;
import ghidra.app.util.task.OpenProgramRequest;
import ghidra.app.util.task.OpenProgramTask;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.InvalidNameException;
import ghidra.util.StringUtilities;
import ghidra.util.*;
import ghidra.util.task.TaskLauncher;
/**
@ -309,28 +294,31 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
private String createVTSessionName(String sourceName, String destinationName) {
// if together they are within the bounds just return session name with both full names
if (sourceName.length() + destinationName.length() <= 2 * VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
if (sourceName.length() + destinationName.length() <= 2 *
VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
return "VT_" + sourceName + "_" + destinationName;
}
// give destination name all space not used by source name
if (sourceName.length() < VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
int leftover = VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH - sourceName.length();
destinationName =
StringUtilities.trimMiddle(destinationName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover);
destinationName = StringUtilities.trimMiddle(destinationName,
VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover);
return "VT_" + sourceName + "_" + destinationName;
}
// give source name all space not used by destination name
if (destinationName.length() < VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
int leftover = VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH - destinationName.length();
sourceName = StringUtilities.trimMiddle(sourceName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover);
sourceName = StringUtilities.trimMiddle(sourceName,
VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover);
return "VT_" + sourceName + "_" + destinationName;
}
// if both too long, shorten both of them
sourceName = StringUtilities.trimMiddle(sourceName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH);
destinationName = StringUtilities.trimMiddle(destinationName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH);
destinationName =
StringUtilities.trimMiddle(destinationName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH);
return "VT_" + sourceName + "_" + destinationName;
}
@ -418,16 +406,17 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
state.put(VTWizardStateKey.NEW_SESSION_FOLDER, folder);
}
private void openProgram(ProgramInfo programInfo) {
private boolean openProgram(ProgramInfo programInfo) {
if (programInfo.hasProgram()) {
return; // already open
return true; // already open
}
OpenProgramTask openProgramTask = new OpenProgramTask(programInfo.getFile(), tool);
new TaskLauncher(openProgramTask, tool.getActiveWindow());
OpenProgramRequest openProgram = openProgramTask.getOpenProgram();
programInfo.setProgram(openProgram != null ? openProgram.getProgram() : null);
return programInfo.hasProgram();
}
@Override
@ -480,19 +469,25 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
DomainFile file = folder.getFile(name);
if (file != null) {
notifyListenersOfStatusMessage(
"'" + file.getPathname() + "' is the name of an existing domain file");
"'" + file.getPathname() + "' is the name of an existing project file");
return false;
}
openProgram(sourceProgramInfo);
if (!sourceProgramInfo.hasProgram()) {
// Known Issue: Opening programs before comitted to using them (i.e., Next is clicked) seems
// premature and will subject user to prompts about possible checkout and/or upgrades
// with possible slow re-disassembly (see GP-4151)
if (!isValidDestinationProgramFile() || !isValidSourceProgramFile()) {
return false;
}
if (!openProgram(sourceProgramInfo)) {
notifyListenersOfStatusMessage(
"Can't open source program " + sourceProgramInfo.getName());
return false;
}
openProgram(destinationProgramInfo);
if (!destinationProgramInfo.hasProgram()) {
if (!openProgram(destinationProgramInfo)) {
notifyListenersOfStatusMessage(
"Can't open destination program " + destinationProgramInfo.getName());
return false;
@ -502,6 +497,29 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
return true;
}
private boolean isValidSourceProgramFile() {
try {
VTSessionFileUtil.validateSourceProgramFile(sourceProgramInfo.file, false);
}
catch (Exception e) {
notifyListenersOfStatusMessage(e.getMessage());
return false;
}
return true;
}
private boolean isValidDestinationProgramFile() {
try {
VTSessionFileUtil.validateDestinationProgramFile(destinationProgramInfo.file, false,
false);
}
catch (Exception e) {
notifyListenersOfStatusMessage(e.getMessage());
return false;
}
return true;
}
@Override
public void addDependencies(WizardState<VTWizardStateKey> state) {
// none

View file

@ -44,8 +44,7 @@ public class VTNewSessionWizardManager extends AbstractMagePanelManager<VTWizard
@Override
protected List<MagePanel<VTWizardStateKey>> createPanels() {
List<MagePanel<VTWizardStateKey>> panels =
new ArrayList<>();
List<MagePanel<VTWizardStateKey>> panels = new ArrayList<>();
panels.add(new NewSessionPanel(controller.getTool()));
panels.add(new PreconditionsPanel(this));
panels.add(new SummaryPanel());

View file

@ -115,9 +115,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionNoSelectionUnlimitedAddresses() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -170,9 +169,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionNoSelectionLimitAddressesToEntireProgram() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -231,9 +229,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionNoSelectionLimitAddressesToMyOwn() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -292,9 +289,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionNoSelectionLimitAddressesToMyOwnChanged() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -366,9 +362,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionWithSelectionLimitAddressesToEntireProgram() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -429,9 +424,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionWithSelectionLimitAddressesToSelection() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -492,9 +486,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionWithSelectionLimitAddressesToMyOwn() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -568,9 +561,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddToSessionWithSelectionLimitAddressesToMyOwnThenBackNext() throws Exception {
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";
@ -671,9 +663,8 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
public void testAddToSessionResultingInNoMatchesFound() throws Exception {
setErrorGUIEnabled(true);
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
String sessionName = "Untitled";

View file

@ -78,9 +78,8 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin);
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session));

View file

@ -81,9 +81,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
VTPlugin plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin);
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session));
@ -390,8 +389,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
}
@Test
public void testApplyMatch_ReplaceSignature_CustomSourceAndDest()
throws Exception {
public void testApplyMatch_ReplaceSignature_CustomSourceAndDest() throws Exception {
useMatch("0x00401040", "0x00401040");
@ -442,8 +440,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
}
@Test
public void testApplyMatch_ReplaceSignature_NormalSourceCustomDest()
throws Exception {
public void testApplyMatch_ReplaceSignature_NormalSourceCustomDest() throws Exception {
useMatch("0x00401040", "0x00401040");
@ -666,9 +663,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
env.release(destinationProgram);
destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session));
useMatch("0x00401040", "0x00010938");

View file

@ -94,9 +94,8 @@ public class VTMatchApplyTest extends AbstractGhidraHeadedIntegrationTest {
plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin);
session =
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session));

View file

@ -79,8 +79,7 @@ public abstract class AbstractCorrelatorTest extends AbstractGhidraHeadedIntegra
protected void exerciseFunctionsForFactory(final VTProgramCorrelatorFactory factory,
AddressSetView sourceSetThatShouldBeFound) throws Exception {
String name = factory.getName();
VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
VTSession session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
try {
int sessionTransaction = session.startTransaction(name);
@ -145,8 +144,7 @@ public abstract class AbstractCorrelatorTest extends AbstractGhidraHeadedIntegra
protected void exercisePreciseMatchesForFactory(VTProgramCorrelatorFactory factory,
Map<Address, Address> map) throws Exception {
String name = factory.getName();
VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
VTSession session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
try {
int sessionTransaction = session.startTransaction(name);

View file

@ -348,7 +348,7 @@ public abstract class AbstractVTMarkupItemTest extends AbstractGhidraHeadedInteg
}
protected VTSessionDB createNewSession() throws Exception {
return VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
return new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this);
}

View file

@ -81,8 +81,7 @@ public class VTBaseTestCase extends AbstractGenericTest {
}
public VTSessionDB createVTSession() throws IOException {
return VTSessionDB.createVTSession("Test DB", sourceProgram, destinationProgram,
VTTestUtils.class);
return new VTSessionDB("Test DB", sourceProgram, destinationProgram, VTTestUtils.class);
}
public static int getRandomInt() {

View file

@ -69,7 +69,7 @@ public class VTTestEnv extends TestEnv {
sourceProgram = getProgram(sourceProgramName);
destinationProgram = getProgram(destinationProgramName);
session = VTSessionDB.createVTSession("Test", sourceProgram, destinationProgram, getTool());
session = new VTSessionDB("Test", sourceProgram, destinationProgram, getTool());
VTProgramCorrelator correlator = factory.createCorrelator(sourceProgram,
sourceProgram.getMemory(), destinationProgram, destinationProgram.getMemory(), null);
@ -111,7 +111,7 @@ public class VTTestEnv extends TestEnv {
}
private VTSessionDB createAndOpenVTSession() throws IOException {
session = VTSessionDB.createVTSession("Test", sourceProgram, destinationProgram, getTool());
session = new VTSessionDB("Test", sourceProgram, destinationProgram, getTool());
runSwing(() -> controller.openVersionTrackingSession(session), false);

View file

@ -53,7 +53,7 @@ public class StubVTController implements VTController {
}
@Override
public void openVersionTrackingSession(DomainFile domainFile) {
public boolean openVersionTrackingSession(DomainFile domainFile) {
throw new UnsupportedOperationException();
}

View file

@ -50,8 +50,8 @@ public abstract class Transaction implements AutoCloseable {
/**
* End this transaction if currently active.
* @param commit true if changes shuold be commited, false if all changes in this transaction
* shuold be discarded (i.e., rollback). If this is a "sub-transaction" and commit is false,
* @param commit true if changes should be commited, false if all changes in this transaction
* should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is false,
* the larger transaction will rollback upon completion.
* @return true if changes have been commited or false if nothing to commit or commit parameter
* was specified as false.
@ -115,5 +115,5 @@ public abstract class Transaction implements AutoCloseable {
endTransaction(commit);
}
}
}

View file

@ -60,7 +60,7 @@ public class DomainFileProxy implements DomainFile {
}
DomainFileProxy(String name, String parentPath, DomainObjectAdapter doa, int version,
String fileID, ProjectLocator projectLocation) {
String fileID, ProjectLocator projectLocation) throws IOException {
this(name, doa);
this.parentPath = parentPath;

View file

@ -94,7 +94,6 @@ public abstract class DomainObjectAdapter implements DomainObject {
consumers = new ArrayList<Object>();
consumers.add(consumer);
if (!UserData.class.isAssignableFrom(getClass())) {
// UserData instances do not utilize DomainFile storage
domainFile = new DomainFileProxy(name, this);
}
}
@ -185,7 +184,12 @@ public abstract class DomainObjectAdapter implements DomainObject {
return temporary;
}
protected void setDomainFile(DomainFile df) {
/**
* Set the {@link DomainFile} associated with this instance.
* @param df domain file
* @throws DomainObjectException if a severe failure occurs during the operation.
*/
protected void setDomainFile(DomainFile df) throws DomainObjectException {
if (df == null) {
throw new IllegalArgumentException("DomainFile must not be null");
}
@ -197,7 +201,6 @@ public abstract class DomainObjectAdapter implements DomainObject {
domainFile = df;
fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.FILE_CHANGED, oldDf, df));
fileChangeListeners.invoke().domainFileChanged(this);
}
protected void close() {

View file

@ -528,6 +528,9 @@ public class GhidraFileData {
projectData.clearDomainObject(getPathname());
// generate IOException
Throwable cause = e.getCause();
if (cause == null) {
cause = e;
}
if (cause instanceof IOException) {
throw (IOException) cause;
}
@ -831,9 +834,12 @@ public class GhidraFileData {
}
/**
* Returns whether the object is read-only. From a framework point of view a read-only object
* can never be changed.
* @return true if read-only
* Returns whether this file is explicitly marked as read-only. This method is only supported
* by the local file system and does not apply to a versioned file that is not checked-out.
* A versioned file that is not checked-out will always return false, while a
* {@link DomainFileProxy} will always return true.
* From a framework point of view a read-only file can never be changed.
* @return true if this file is marked read-only
*/
boolean isReadOnly() {
synchronized (fileSystem) {

View file

@ -158,7 +158,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
toolFrame.addWindowListener(windowListener);
AppInfo.setFrontEndTool(this);
AppInfo.setActiveProject(getProject());
initFrontEndOptions();
}
@ -408,7 +407,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
configureToolAction.setEnabled(true);
setProject(project);
AppInfo.setActiveProject(project);
plugin.setActiveProject(project);
firePluginEvent(new ProjectPluginEvent(getClass().getSimpleName(), project));
}
@ -616,7 +614,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
// Treat setVisible(false) as a dispose, as this is the only time we should be hidden
AppInfo.setFrontEndTool(null);
AppInfo.setActiveProject(null);
dispose();
}
}
@ -645,9 +642,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
return isConfigurable();
}
};
MenuData menuData =
new MenuData(new String[] { ToolConstants.MENU_FILE, "Install Extensions" }, null,
CONFIGURE_GROUP);
MenuData menuData = new MenuData(
new String[] { ToolConstants.MENU_FILE, "Install Extensions" }, null, CONFIGURE_GROUP);
menuData.setMenuSubGroup(CONFIGURE_GROUP + 2);
installExtensionsAction.setMenuBarData(menuData);

View file

@ -331,9 +331,12 @@ public interface DomainFile extends Comparable<DomainFile> {
public void setReadOnly(boolean state) throws IOException;
/**
* Returns whether the object is read-only. From a framework point of view a read-only object
* can never be changed.
* @return true if read-only
* Returns whether this file is explicitly marked as read-only. This method is only supported
* by the local file system and does not apply to a versioned file that is not checked-out.
* A versioned file that is not checked-out will always return false, while a
* {@link DomainFileProxy} will always return true.
* From a framework point of view a read-only file can never be changed.
* @return true if this file is marked read-only
*/
public boolean isReadOnly();

View file

@ -27,6 +27,7 @@ import org.jdom.output.XMLOutputter;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.data.TransientDataManager;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.project.tool.GhidraToolTemplate;
@ -291,16 +292,16 @@ public class DefaultProject implements Project {
throw new IOException("Invalid Ghidra URL specified: " + url);
}
ProjectData projectData = otherViewsMap.get(url);
if (projectData == null) {
projectData = openProjectView(url);
ProjectData viewedProjectData = otherViewsMap.get(url);
if (viewedProjectData == null) {
viewedProjectData = openProjectView(url);
}
if (projectData != null && visible && visibleViews.add(url)) {
if (viewedProjectData != null && visible && visibleViews.add(url)) {
notifyVisibleViewAdded(url);
}
return projectData;
return viewedProjectData;
}
}
@ -378,6 +379,11 @@ public class DefaultProject implements Project {
synchronized (otherViewsMap) {
isClosed = true;
// Clear active project if this is the current active project.
if (AppInfo.getActiveProject() == this) {
AppInfo.setActiveProject(null);
}
for (DefaultProjectData dataMgr : otherViewsMap.values()) {
if (dataMgr != null) {
dataMgr.close();

View file

@ -28,6 +28,7 @@ import ghidra.framework.GenericRunInfo;
import ghidra.framework.ToolUtils;
import ghidra.framework.client.*;
import ghidra.framework.data.TransientDataManager;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.protocol.ghidra.GhidraURL;
@ -111,6 +112,8 @@ public class DefaultProjectManager implements ProjectManager {
lastOpenedProject = projectLocator;
updatePreferences();
}
AppInfo.setActiveProject(currentProject);
return currentProject;
}
@ -138,6 +141,7 @@ public class DefaultProjectManager implements ProjectManager {
try {
currentProject = new DefaultProject(this, projectLocator, resetOwner);
AppInfo.setActiveProject(currentProject);
if (doRestore) {
currentProject.restore();
}
@ -166,6 +170,7 @@ public class DefaultProjectManager implements ProjectManager {
}
}
}
AppInfo.setActiveProject(null);
return null;
}