GP-4085 Added ability to add VTSession to a shared repository

This commit is contained in:
ghidra1 2023-12-07 13:31:56 -05:00
parent b8f004c792
commit c3386b72a2
33 changed files with 1063 additions and 565 deletions

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

@ -13,6 +13,7 @@
* 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:
// 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
@ -129,67 +130,72 @@ public class AutoVersionTrackingScript extends GhidraScript {
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) {
return;
}
// Need to end the script transaction or it interferes with vt things that need locks
end(true);
VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor);
}
// 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;
VTSession session = null;
try {
if (isCurrentProgramSourceProg) {
sourceProgram = currentProgram;
destinationProgram = otherProgram;
}
else {
destinationProgram = currentProgram;
sourceProgram = otherProgram;
}
}
if (sourceProgram == null || destinationProgram == null) {
return;
}
ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap);
// Need to end the script transaction or it interferes with vt things that need locks
end(true);
AutoVersionTrackingTask autoVtTask = new AutoVersionTrackingTask(session, vtOptions);
session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
TaskLauncher.launch(autoVtTask);
if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor);
}
// 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()) {
// 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;
}
}
ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap);
AutoVersionTrackingTask autoVtTask = new AutoVersionTrackingTask(session, vtOptions);
TaskLauncher.launch(autoVtTask);
// Save destination program and session changes
otherProgram.save("Updated with Auto Version Tracking", monitor);
session.save();
}
println(autoVtTask.getStatusMsg());
otherProgram.release(this);
println(autoVtTask.getStatusMsg());
}
finally {
if (otherProgram != null) {
otherProgram.release(this);
}
if (session != null) {
session.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

@ -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;
}