GP-4410 - Version Tracking - Added support for deleting matches; Added table column filters

This commit is contained in:
dragonmacher 2024-07-18 13:54:26 -04:00
parent 76977bd514
commit 9f73d23ee4
36 changed files with 1335 additions and 699 deletions

View file

@ -26,6 +26,7 @@ project.ext.excludeFromParallelIntegrationTests = true
dependencies {
api project(":Base")
runtimeOnly project(":CodeCompare")
testImplementation project(path: ':Project', configuration: 'testArtifacts')
testImplementation project(path: ':SoftwareModeling', configuration: 'testArtifacts')

View file

@ -315,9 +315,56 @@
<P align="left"><A name="Clear_Match"></A>The <b>Clear Match</b> <IMG src="images/undo-apply.png" border="0">
action will reset the match to unaccepted and undo any applied markup.</P>
<P align="left"><A name="Remove_Match"></A>The <b>Remove Match</b> <IMG src="images/edit-delete.png" border="0">
action will remove a manually created match from the matches table.</P>
action will remove the selected match(es).</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/warning.png" alt="Note" border="0">
As of Ghidra 11.2, Version Tracking supports deleting matches. Any match that has
not been accepted can be deleted without confirmation. However, if you attempt to
delete an <B>accepted match</B> that is the <B>last match for an association</B>, then
you will be prompted to confirm your decision.
</P>
<P>
Generally, we suggest users should not delete accepted matches. The more matches that
are accepted, the better the Version Tracking results, since user choices affect
future match scores. Keeping accepted matches and the applied markup provides
future analysis with more corroborating details. Contrastingly, deleting accepted
matches while keeping applied markup will remove supporting evidence that the user has
already substantiated.
</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/tip.png">An alternative to deleting matches is to
simply filter them out of the table once they have been applied. You can also tag
any matches you wish to ignore and then use the <A HREF="#VT_Advanced_Filters">
advanced filters</a> to hide any matches with those tags.
</P>
</BLOCKQUOTE>
<P>
It is important to understand what happens in Version Tracking when deleting a match.
You will have to make a decision before deleting whether you want to keep any changes
made to the destination program when you accepted a given match or whether you wish to
remove that markup. When deleting an accepted match:
</P>
<BLOCKQUOTE>
<P>
<U>To keep all applied markup</U>, simply delete the match and, when
prompted, choose <B>Delete Accepted Matches</B>. This choice will delete the match and
its markup items, but <B>any applied markup item content will remain in the destination
program.</B> Alternatively, when prompted, you can choose <B>Finish</B> which will
close the prompt dialog and will not delete the remaining accepted matches or markup.
<P>
<U>To remove all applied markup</U>, then you must first
<A HREF="#Clear_Match">clear</A> the match before executing the remove action. The clear
action will remove applied markup. After clearing the match, then you can remove
the match and no markup will remain in the destination program.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P align="left"><A name="Make_Selections"></A>The <b>Make Selections</b> <IMG src="Icons.MAKE_SELECTION_ICON" border="0">
action will create selections in the source and destination tools for all matches selected in the table.</P>
@ -400,79 +447,101 @@
<H2><A name="Match_Filters"></A>Match Filters</H2>
<BLOCKQUOTE>
<P align="left">The match table has an extensive assortment of filters. There
are several commonly used filter controls at the bottom of the table:
<ol>
<li><b>Text Filter</b> - allows you to filter based on any text in the table
</li>
<li><b>Score Filter</b> - allows you to filter on a range of scores. All scores
are between 0 and 1
</li>
<li><b>Confidence Filter</b> - allows you to filter a range of confidence values.
All confidence values will be greater than -9.999 and smaller than 9.999.
</li>
<li><b>Length Filter</b> - is used to filter out
functions that are smaller than some number
</li>
</ol>
</P>
<P>Finally, the <IMG src="images/view-filter.png" border="0"> will show the ancillary filters
available. The table below lists and describes the available filters. When an ancillary
filter is applied, the icon will change to <IMG src="images/lightbulb.png" border="0"> .
Further, the icon may occasionally flash as a reminder that there is a filter applied.</P>
<BR>
<TABLE border="1" width="90%">
<TR>
<TH nowrap>Filter Name</TH>
<TH>Description</TH>
</TR>
<TR>
<TD valign="top">Match Type</TD>
<TD valign="top">This filter allows the user to show only function or data matches.</TD>
</TR>
<TR>
<TD valign="top" nowrap>Association Status</TD>
<TD valign="top">This filter allows the user to show only matches whose assocation
has one of the included status types. A useful setting
for this filter is to turn off all but the <B>Available</B> status. This will cause the
table to act like a "To Do" list.</TD>
</TR>
<TR>
<TD valign="top">Symbol Type</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination labels are of one of the included symbol types.</TD>
</TR>
<TR>
<TD valign="top">Algorithms</TD>
<TD valign="top">This filter allows the user to show only matches that were generated
by one of the included types of correlating algorithms</TD>
</TR>
<TR>
<TD valign="top">Address Range</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination address is within the specified range.</TD>
</TR>
<TR>
<TD valign="top">Tags</TD>
<TD valign="top">This filter allows the user to show only matches whose tag is an
included tag.</TD>
</TR>
<BLOCKQUOTE>
<H3>Table Filters</H3>
</TABLE>
<BLOCKQUOTE>
<P align="left">The match table has an extensive assortment of filters. There
are several commonly used filter controls at the bottom of the table:
<ol>
<li><b>Text Filter</b> - allows you to filter based on any text in the table
</li>
<li><b>Score Filter</b> - allows you to filter on a range of scores. All scores
are between 0 and 1
</li>
<li><b>Confidence Filter</b> - allows you to filter a range of confidence values.
All confidence values will be greater than -9.999 and smaller than 9.999.
</li>
<li><b>Length Filter</b> - is used to filter out
functions that are smaller than some number
</li>
</ol>
</P>
</BLOCKQUOTE>
<A NAME="VT_Advanced_Filters"></A>
<H3>Advanced Filters</H3>
<BLOCKQUOTE>
<P>Finally, the <IMG src="icon.version.tracking.unfiltered" border="0"> will show the ancillary filters
available. The table below lists and describes the available filters. When an ancillary
filter is applied, the icon will change to <IMG src="icon.version.tracking.filtered" border="0"> .
Further, the icon may occasionally flash as a reminder that there is a filter applied.</P>
<BR>
<TABLE border="1" width="90%">
<TR>
<TH nowrap>Filter Name</TH>
<TH>Description</TH>
</TR>
<TR>
<TD valign="top">Match Type</TD>
<TD valign="top">This filter allows the user to show only function or data matches.</TD>
</TR>
<TR>
<TD valign="top" nowrap>Association Status</TD>
<TD valign="top">This filter allows the user to show only matches whose assocation
has one of the included status types. A useful setting
for this filter is to turn off all but the <B>Available</B> status. This will cause the
table to act like a "To Do" list.</TD>
</TR>
<TR>
<TD valign="top">Symbol Type</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination labels are of one of the included symbol types.</TD>
</TR>
<TR>
<TD valign="top">Algorithms</TD>
<TD valign="top">This filter allows the user to show only matches that were generated
by one of the included types of correlating algorithms</TD>
</TR>
<TR>
<TD valign="top">Address Range</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination address is within the specified range.</TD>
</TR>
<TR>
<TD valign="top">Tags</TD>
<TD valign="top">This filter allows the user to show only matches whose tag is an
included tag.</TD>
</TR>
</TABLE>
</BLOCKQUOTE>
<H3>Table Column Filters</H3>
<BLOCKQUOTE>
The matches table also supports
<A HREF="help/topics/Trees/GhidraTreeFilter.html#Column_Filters">
Table Column Filters</A> for creating complex filters for individual table columns.
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE> <!-- end of top-level blockquote -->

View file

@ -21,8 +21,7 @@ import java.io.IOException;
import java.util.*;
import db.*;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.impl.*;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTAssociationStatusException;
import ghidra.framework.data.OpenMode;
@ -138,33 +137,13 @@ public class AssociationDatabaseManager implements VTAssociationManager {
return createMarkupItemDB(markupItemStorage);
}
void removeMarkupItem(MarkupItemStorageDB appliedMarkupItemDB) {
// non-interface method; internal API use
public void removeStoredMarkupItems(List<MarkupItemImpl> impls) {
VTAssociationDB association = (VTAssociationDB) appliedMarkupItemDB.getAssociation();
validateAcceptedState(appliedMarkupItemDB, association);
try {
markupItemTableAdapter.removeMatchMarkupItemRecord(appliedMarkupItemDB.getKey());
}
catch (IOException e) {
session.dbError(e);
}
}
private void validateAcceptedState(MarkupItemStorageDB appliedItem,
VTAssociationDB association) {
//
// For any 'applied' markup item we assume that its association will be 'ACCEPTED'. The
// exception to this rule is when we have markup items in the database, but that are not
// applied (like when we change the destination address without applying)
//
VTAssociationStatus associationStatus = association.getStatus();
VTMarkupItemStatus status = appliedItem.getStatus();
if (status.isUnappliable()) {
if (associationStatus != ACCEPTED) {
throw new AssertException("Cannot have an applied markup item with an " +
"association that is not ACCEPTED");
for (MarkupItemImpl impl : impls) {
MarkupItemStorage storage = impl.getStorage();
if (storage instanceof MarkupItemStorageDB storageDb) {
removeMarkupRecord(storageDb.getKey());
}
}
}
@ -251,17 +230,31 @@ public class AssociationDatabaseManager implements VTAssociationManager {
}
void removeAssociation(VTAssociation association) {
VTAssociationDB existingAssociation = (VTAssociationDB) association;
long id = existingAssociation.getKey();
// Update the association status so that we update any blocked associations
VTAssociationDB associationDB = (VTAssociationDB) association;
VTAssociationStatus status = association.getStatus();
if (status == ACCEPTED) {
associationDB.setStatus(AVAILABLE);
associationDB.setInvalid();
unblockRelatedAssociations(associationDB);
for (AssociationHook hook : associationHooks) {
hook.associationCleared(associationDB);
}
}
VTAssociationDB associationDb = (VTAssociationDB) association;
long id = associationDb.getKey();
try {
associationDb.removeMarkupItems();
associationTableAdapter.removeAssociaiton(id);
session.setChanged(VTEvent.ASSOCIATION_REMOVED, existingAssociation, null);
session.setChanged(VTEvent.ASSOCIATION_REMOVED, associationDb, null);
}
catch (IOException e) {
session.dbError(e);
}
associationCache.delete(id);
existingAssociation.setInvalid();
associationDb.setInvalid();
}
@ -507,7 +500,7 @@ public class AssociationDatabaseManager implements VTAssociationManager {
throws VTAssociationStatusException {
if (association.hasAppliedMarkupItems()) {
throw new VTAssociationStatusException(
"VTMarkupItemManager contains applied " + "markup items");
"VTMarkupItemManager contains applied markup items");
}
}
@ -623,9 +616,9 @@ public class AssociationDatabaseManager implements VTAssociationManager {
associationHooks.remove(hook);
}
void removeMarkupRecord(DBRecord record) {
void removeMarkupRecord(long key) {
try {
markupItemTableAdapter.removeMatchMarkupItemRecord(record.getKey());
markupItemTableAdapter.removeMarkupItemRecord(key);
}
catch (IOException e) {
session.dbError(e);

View file

@ -135,7 +135,7 @@ public class MarkupItemStorageDB extends DatabaseObject implements MarkupItemSto
try {
MarkupItemStorage storage = new MarkupItemStorageImpl(getAssociation(), getMarkupType(),
getSourceAddress(), getDestinationAddress(), getDestinationAddressSource());
associationManager.removeMarkupRecord(record);
associationManager.removeMarkupRecord(record.getKey());
return storage;
}
finally {
@ -144,7 +144,8 @@ public class MarkupItemStorageDB extends DatabaseObject implements MarkupItemSto
}
@Override
public MarkupItemStorage setDestinationAddress(Address destinationAddress, String addressSource) {
public MarkupItemStorage setDestinationAddress(Address destinationAddress,
String addressSource) {
if (destinationAddress == null) {
destinationAddress = Address.NO_ADDRESS;
}

View file

@ -296,4 +296,8 @@ public class VTAssociationDB extends DatabaseObject implements VTAssociation {
public boolean hasAppliedMarkupItems() {
return markupManager.hasAppliedMarkupItems();
}
void removeMarkupItems() {
markupManager.removeMarkupItems();
}
}

View file

@ -60,7 +60,7 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
public abstract RecordIterator getRecords() throws IOException;
public abstract void removeMatchMarkupItemRecord(long key) throws IOException;
public abstract void removeMarkupItemRecord(long key) throws IOException;
public abstract DBRecord getRecord(long key) throws IOException;
@ -70,5 +70,6 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
public abstract int getRecordCount();
public abstract DBRecord createMarkupItemRecord(MarkupItemStorage markupItem) throws IOException;
public abstract DBRecord createMarkupItemRecord(MarkupItemStorage markupItem)
throws IOException;
}

View file

@ -15,14 +15,11 @@
*/
package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.ADDRESS_SOURCE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.ASSOCIATION_KEY_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.DESTINATION_ADDRESS_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.MARKUP_TYPE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.ORIGINAL_DESTINATION_VALUE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.SOURCE_ADDRESS_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.SOURCE_VALUE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.STATUS_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.*;
import java.io.IOException;
import db.*;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.api.markuptype.VTMarkupTypeFactory;
@ -34,10 +31,6 @@ import ghidra.program.model.listing.Program;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import db.*;
public class VTMatchMarkupItemTableDBAdapterV0 extends VTMatchMarkupItemTableDBAdapter {
private Table table;
@ -71,20 +64,20 @@ public class VTMatchMarkupItemTableDBAdapterV0 extends VTMatchMarkupItemTableDBA
record.setLongValue(ASSOCIATION_KEY_COL.column(), association.getKey());
record.setString(ADDRESS_SOURCE_COL.column(), markupItem.getDestinationAddressSource());
record.setLongValue(SOURCE_ADDRESS_COL.column(), getAddressID(sourceProgram,
markupItem.getSourceAddress()));
record.setLongValue(SOURCE_ADDRESS_COL.column(),
getAddressID(sourceProgram, markupItem.getSourceAddress()));
Address destinationAddress = markupItem.getDestinationAddress();
if (destinationAddress != null) {
record.setLongValue(DESTINATION_ADDRESS_COL.column(), getAddressID(destinationProgram,
markupItem.getDestinationAddress()));
record.setLongValue(DESTINATION_ADDRESS_COL.column(),
getAddressID(destinationProgram, markupItem.getDestinationAddress()));
}
record.setShortValue(MARKUP_TYPE_COL.column(),
(short) VTMarkupTypeFactory.getID(markupItem.getMarkupType()));
record.setString(SOURCE_VALUE_COL.column(), Stringable.getString(
markupItem.getSourceValue(), sourceProgram));
record.setString(ORIGINAL_DESTINATION_VALUE_COL.column(), Stringable.getString(
markupItem.getDestinationValue(), destinationProgram));
record.setString(SOURCE_VALUE_COL.column(),
Stringable.getString(markupItem.getSourceValue(), sourceProgram));
record.setString(ORIGINAL_DESTINATION_VALUE_COL.column(),
Stringable.getString(markupItem.getDestinationValue(), destinationProgram));
record.setByteValue(STATUS_COL.column(), (byte) markupItem.getStatus().ordinal());
table.putRecord(record);
@ -97,7 +90,7 @@ public class VTMatchMarkupItemTableDBAdapterV0 extends VTMatchMarkupItemTableDBA
}
@Override
public void removeMatchMarkupItemRecord(long key) throws IOException {
public void removeMarkupItemRecord(long key) throws IOException {
table.deleteRecord(key);
}

View file

@ -25,8 +25,6 @@ import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import db.*;
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.framework.data.OpenMode;
@ -186,37 +184,47 @@ public class VTMatchSetDB extends DatabaseObject implements VTMatchSet {
@Override
public boolean removeMatch(VTMatch match) {
if (!(match instanceof VTMatchDB)) {
return false;
}
if (!match.getMatchSet().hasRemovableMatches()) {
return false;
}
VTMatchDB matchDB = (VTMatchDB) match;
if (!(match instanceof VTMatchDB matchDb)) {
// this should not be possible from the UI
throw new IllegalArgumentException("Can only remove matches saved to the database");
}
VTAssociation association = match.getAssociation();
// Remove the association if it was the only remaining match for that association.
AssociationDatabaseManager associationManager = session.getAssociationManagerDBM();
List<VTMatch> matches = session.getMatches(association);
if (matches.size() == 1 && association.getStatus() == VTAssociationStatus.ACCEPTED) {
return false; // can't remove the last match if the association is accepted
// This method prevents deleting the association if it is accepted, as it would cause
// the user to lose potentially valuable information without realizing it. To work
// around that issue when calling this method, the user can first un-accept the match.
return false;
}
// Remove the match record
deleteMatch(matchDb);
return true;
}
@Override
public void deleteMatch(VTMatch match) {
if (!(match instanceof VTMatchDB matchDb)) {
// this should not be possible from the UI
throw new IllegalArgumentException("Can only remove matches saved to the database");
}
VTAssociation association = match.getAssociation();
Address sourceAddress = association.getSourceAddress();
Address destinationAddress = association.getDestinationAddress();
try {
lock.acquire();
long matchKey = matchDB.getKey();
long matchKey = matchDb.getKey();
boolean deleted = matchTableAdapter.deleteRecord(matchKey);
if (deleted) {
matchCache.delete(matchKey);
if (matches.size() == 1) {
List<VTMatch> matches = session.getMatches(association);
if (matches.isEmpty()) {
// if last match, remove association
associationManager.removeAssociation(association);
AssociationDatabaseManager manager = session.getAssociationManagerDBM();
manager.removeAssociation(association);
}
}
}
@ -229,7 +237,6 @@ public class VTMatchSetDB extends DatabaseObject implements VTMatchSet {
DeletedMatch deletedMatch = new DeletedMatch(sourceAddress, destinationAddress);
session.setObjectChanged(VTEvent.MATCH_DELETED, match, deletedMatch, null);
return true;
}
@Override
@ -349,14 +356,6 @@ public class VTMatchSetDB extends DatabaseObject implements VTMatchSet {
}
}
@Override
public boolean hasRemovableMatches() {
VTProgramCorrelatorInfo info = getProgramCorrelatorInfo();
String correlatorClassName = info.getCorrelatorClassName();
return correlatorClassName.equals(ManualMatchProgramCorrelator.class.getName()) ||
correlatorClassName.equals(ImpliedMatchProgramCorrelator.class.getName());
}
@Override
public String toString() {
return "Match Set " + getID() + " - " + getMatchCount() + " matches [Correlator=" +

View file

@ -501,4 +501,8 @@ public class MarkupItemImpl implements VTMarkupItem {
newStatus);
}
// non-interface method
public MarkupItemStorage getStorage() {
return markupItemStorage;
}
}

View file

@ -15,14 +15,14 @@
*/
package ghidra.feature.vt.api.impl;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.feature.vt.api.db.*;
import ghidra.feature.vt.api.main.VTMarkupItem;
import ghidra.feature.vt.api.main.VTMarkupItemStatus;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import java.util.*;
public class MarkupItemManagerImpl {
@ -70,7 +70,7 @@ public class MarkupItemManagerImpl {
return Collections.unmodifiableList(markupItems);
}
protected List<VTMarkupItem> createMarkupItems(TaskMonitor monitor) throws CancelledException {
private List<VTMarkupItem> createMarkupItems(TaskMonitor monitor) throws CancelledException {
Collection<VTMarkupItem> generatedMarkupItems = getGeneratedMarkupItems(monitor);
Collection<VTMarkupItem> databaseMarkupItems = getStoredMarkupItems(monitor);
@ -104,18 +104,6 @@ public class MarkupItemManagerImpl {
return false;
}
private Collection<VTMarkupItem> getStoredMarkupItems(TaskMonitor monitor)
throws CancelledException {
AssociationDatabaseManager associationDBM = association.getAssociationManagerDB();
Collection<MarkupItemStorageDB> appliedMarkupItems =
associationDBM.getAppliedMarkupItems(monitor, association);
List<VTMarkupItem> list = new ArrayList<VTMarkupItem>();
for (MarkupItemStorageDB markupItemStorageDB : appliedMarkupItems) {
list.add(new MarkupItemImpl(markupItemStorageDB));
}
return list;
}
private List<VTMarkupItem> replaceGeneratedMarkupItemsWithDBMarkupItems(
Collection<VTMarkupItem> generatedMarkupItems,
Collection<VTMarkupItem> databaseMarkupItems) {
@ -142,8 +130,45 @@ public class MarkupItemManagerImpl {
markupItem.getSourceAddress().toString(true);
}
public void clearCache() {
// synchronized due to write of 'markupItems'
public synchronized void clearCache() {
markupItems = EMPTY_LIST;
}
private Collection<VTMarkupItem> getStoredMarkupItems(TaskMonitor monitor)
throws CancelledException {
AssociationDatabaseManager associationDBM = association.getAssociationManagerDB();
Collection<MarkupItemStorageDB> appliedMarkupItems =
associationDBM.getAppliedMarkupItems(monitor, association);
List<VTMarkupItem> list = new ArrayList<VTMarkupItem>();
for (MarkupItemStorageDB markupItemStorageDB : appliedMarkupItems) {
list.add(new MarkupItemImpl(markupItemStorageDB));
}
return list;
}
// synchronized to match getMarkupItems() so we do not have other clients loading items while
// we are processing them
public synchronized void removeMarkupItems() {
List<VTMarkupItem> items;
try {
items = getMarkupItems(TaskMonitor.DUMMY);
}
catch (CancelledException e) {
return; // can't happen with DUMMY
}
List<MarkupItemImpl> impls = items.stream()
.map(item -> (MarkupItemImpl) item)
.filter(impl -> impl.isStoredInDB())
.collect(Collectors.toList());
AssociationDatabaseManager associationDbm = association.getAssociationManagerDB();
associationDbm.removeStoredMarkupItems(impls);
// signal that markup item info has changed and must be reloaded when next needed
clearCache();
}
}

View file

@ -15,13 +15,11 @@
*/
package ghidra.feature.vt.api.impl;
import ghidra.feature.vt.api.correlator.program.ImpliedMatchProgramCorrelator;
import ghidra.feature.vt.api.correlator.program.ManualMatchProgramCorrelator;
import java.util.*;
import ghidra.feature.vt.api.main.*;
import ghidra.program.model.address.Address;
import java.util.*;
public class MatchSetImpl implements VTMatchSet {
private ProgramCorrelatorInfoFake correlatorInfo;
private final VTSession session;
@ -77,11 +75,8 @@ public class MatchSetImpl implements VTMatchSet {
}
@Override
public boolean hasRemovableMatches() {
VTProgramCorrelatorInfo info = getProgramCorrelatorInfo();
String correlatorClassName = info.getCorrelatorClassName();
return correlatorClassName.equals(ManualMatchProgramCorrelator.class.getName()) ||
correlatorClassName.equals(ImpliedMatchProgramCorrelator.class.getName());
public void deleteMatch(VTMatch match) {
throw new UnsupportedOperationException();
}
@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,11 +15,11 @@
*/
package ghidra.feature.vt.api.main;
import java.util.Collection;
import ghidra.feature.vt.api.impl.VTProgramCorrelatorInfo;
import ghidra.program.model.address.Address;
import java.util.Collection;
/**
* Interface for all the matches generated from a single program correlator run.
*
@ -57,14 +56,14 @@ public interface VTMatchSet {
/**
* Returns the number of matches contained in this match set.
* @return
* @return the number of matches contained in this match set.
*/
public int getMatchCount();
/**
* Returns a unique id for this match set. The ids are one-up numbers indicating the order this
* match set was generated in relation to other match sets in the VTSession.
* @return
* @return the id
*/
public int getID();
@ -72,7 +71,7 @@ public interface VTMatchSet {
* Returns a collection of all matches for the given association.
* @param association the association for which to search for matches.
* @return a collection of all matches for the given association.
* @see #getMatches(Address, Address, VTAssociationType)
* @see #getMatches(Address, Address)
*/
public Collection<VTMatch> getMatches(VTAssociation association);
@ -90,16 +89,51 @@ public interface VTMatchSet {
public Collection<VTMatch> getMatches(Address sourceAddress, Address destinationAddress);
/**
* Removes a match from this match set. Note that this operation is only supported for built-in
* match sets "Manual Matches" and "Implied Matches".
* Deletes the given match from this match set.
* <P>
* Note: deleting an <B>ACCEPTED</B> match removes potentially useful corroborating evidence
* from future correlation. Before deleting a match, consider instead filtering matches out of
* the UI that you are finished applying.
* <P>
* If this is the last match that shares the match's association, then the association will also
* be removed, along with any markup items in the database. <B>Any applied markup item data
* will not be changed.</B>
*
* @param match the match
*/
public void deleteMatch(VTMatch match);
/**
* Removes a match from this match set.
* <P>
* If this is the last match that shares the match's association, then the match will only be
* removed if the association is not accepted. In that case, no remove will take place and
* this method will return false.
* <P>
* Note: This method is deprecated. It unfortunately shares a very similar name with its
* replacement, {@link #deleteMatch(VTMatch)}. The replacement method will delete the match
* and the related association and markup items in the database, if the match is the last match
* to use that association. This deprecated method does not remove the remaining association or
* markup items. Historically, this method has been called after clearing the given match and
* its markup. Once this method has been deleted, clients will be responsible for managing the
* markup item state before calling {@link #deleteMatch(VTMatch)}.
*
* @param match the match to remove.
* @return true if the match was removed.
* @throws IllegalArgumentException if a non-database match is passed to this method
* @see #deleteMatch(VTMatch)
* @deprecated use {@link #deleteMatch(VTMatch)}
*/
@Deprecated(since = "11.2", forRemoval = true)
public boolean removeMatch(VTMatch match);
/**
* Returns true if this match set supports removing matches.
* @return true if this match set supports removing matches.
* Returns true
* @return true
* @deprecated this method now always returns true
*/
public boolean hasRemovableMatches();
@Deprecated(since = "11.2", forRemoval = true)
public default boolean hasRemovableMatches() {
return true;
}
}

View file

@ -41,7 +41,6 @@ public class RemoveMatchAction extends DockingAction {
super("Remove", VTPlugin.OWNER);
this.controller = controller;
// setToolBarData(new ToolBarData(ICON, MENU_GROUP));
setPopupMenuData(new MenuData(new String[] { "Remove Match" }, ICON, MENU_GROUP));
setEnabled(false);
setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Remove_Match"));
@ -64,17 +63,7 @@ public class RemoveMatchAction extends DockingAction {
}
VTMatchContext matchContext = (VTMatchContext) context;
List<VTMatch> matches = matchContext.getSelectedMatches();
if (matches.size() == 0) {
return false;
}
if (!isRemovableMatch(matches.get(0))) {
return false; // It must be a single manual match.
}
return true;
}
private boolean isRemovableMatch(VTMatch vtMatch) {
return vtMatch.getMatchSet().hasRemovableMatches();
return !matches.isEmpty();
}
@Override

View file

@ -282,12 +282,6 @@ public abstract class AbstractAddressRangeFilter<T> extends AncillaryFilter<T>
fireStatusChanged(getFilterStatus());
}
@Override
public void clearFilter() {
lowerAddressRangeTextField.setText(MIN_ADDRESS_VALUE.toString());
upperAddressRangeTextField.setText(MAX_ADDRESS_VALUE.toString());
}
@Override
public JComponent getComponent() {
return component;

View file

@ -15,8 +15,7 @@
*/
package ghidra.feature.vt.gui.filters;
import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.APPLIED;
import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.NONE;
import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.*;
import java.awt.Container;
import java.awt.LayoutManager;
@ -151,13 +150,6 @@ public abstract class CheckBoxBasedAncillaryFilter<T> extends AncillaryFilter<T>
return FilterShortcutState.REQUIRES_CHECK;
}
@Override
public void clearFilter() {
for (CheckBoxInfo<T> info : checkBoxInfos) {
info.setSelected(true);
}
}
@Override
public FilterState getFilterState() {
FilterState state = new FilterState(this);

View file

@ -46,8 +46,6 @@ public abstract class Filter<T> {
public abstract FilterEditingStatus getFilterStatus();
public abstract void clearFilter();
public abstract JComponent getComponent();
public void dispose() {

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.
@ -18,12 +17,13 @@ package ghidra.feature.vt.gui.filters;
public interface FilterDialogModel<T> {
public void addFilter( Filter<T> filter );
public void forceRefilter();
/**
* Will be called when the visibility of the dialog using this model has changed
*/
public void dialogVisibilityChanged( boolean isVisible );
public void addFilter(Filter<T> filter);
public void forceRefilter();
/**
* Will be called when the visibility of the dialog using this model has changed
* @param isVisible true if visible
*/
public void dialogVisibilityChanged(boolean isVisible);
}

View file

@ -243,12 +243,6 @@ public class TagFilter extends AncillaryFilter<VTMatch> {
excludedTags = getTagsFromText(tagText);
}
@Override
public void clearFilter() {
excludedTags.clear();
excludedTagsLabel.setText(ALL_TAGS_INCLUDED);
}
@Override
public JComponent getComponent() {
return component;

View file

@ -150,13 +150,14 @@ public class VTPlugin extends Plugin {
private void addCustomPlugins() {
List<String> names = new ArrayList<>(List.of("ghidra.features.codecompare.plugin"));
List<String> names =
new ArrayList<>(List.of("ghidra.features.codecompare.plugin.FunctionComparisonPlugin"));
List<Plugin> plugins = tool.getManagedPlugins();
Set<String> existingNames =
plugins.stream().map(c -> c.getName()).collect(Collectors.toSet());
// Note: we check to see if the plugins we want to add have already been added to the tool.
// We should not needed to do this, but once the tool has been saved with the plugins added,
// We should not need to do this, but once the tool has been saved with the plugins added,
// they will get added again the next time the tool is loaded. Adding this check here seems
// easier than modifying the default to file to load the plugins, since the amount of xml
// required for that is non-trivial.

View file

@ -127,12 +127,6 @@ public abstract class AbstractDoubleRangeFilter<T> extends Filter<T>
return component;
}
@Override
public void clearFilter() {
lowerBoundField.setText(minValue.toString());
upperBoundField.setText(maxValue.toString());
}
@Override
public FilterEditingStatus getFilterStatus() {
FilterEditingStatus lowerStatus = lowerBoundField.getFilterStatus();

View file

@ -89,11 +89,6 @@ public class LengthFilter extends Filter<VTMatch> {
return component;
}
@Override
public void clearFilter() {
textField.setText(DEFAULT_FILTER_VALUE.toString());
}
@Override
public FilterEditingStatus getFilterStatus() {
return textField.getFilterStatus();

View file

@ -34,6 +34,8 @@ import javax.swing.table.*;
import docking.*;
import docking.action.builder.ActionBuilder;
import docking.widgets.table.*;
import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
import docking.widgets.table.columnfilter.ColumnFilterManager;
import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
@ -80,6 +82,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
private AncillaryFilterDialogComponentProvider<VTMatch> ancillaryFilterDialog;
private JButton ancillaryFilterButton;
private ColumnFilterManager<VTMatch> columnFilterManager;
private VTColumnFilter vtColumnFilter;
private FilterIconFlashTimer<VTMatch> iconTimer;
private Set<Filter<VTMatch>> filters = new HashSet<>();
@ -386,8 +390,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
JPanel innerPanel = new JPanel(new HorizontalLayout(4));
innerPanel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
JComponent nameFilterPanel = createTextFilterPanel();
parentPanel.add(nameFilterPanel, BorderLayout.CENTER);
JComponent textFilterPanel = createTextFilterPanel();
parentPanel.add(textFilterPanel, BorderLayout.CENTER);
parentPanel.add(innerPanel, BorderLayout.EAST);
JComponent scoreFilterPanel = createScoreFilterPanel();
@ -409,13 +413,33 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
helpService.registerHelp(parentPanel, filterHelpLocation);
helpService.registerHelp(ancillaryFilterButton, filterHelpLocation);
JButton columnFilterButton = createColumnFilterButton();
innerPanel.add(columnFilterButton);
innerPanel.add(ancillaryFilterButton);
return parentPanel;
}
private JButton createColumnFilterButton() {
String preferenceKey =
matchesTable.getPreferenceKey() + ColumnFilterManager.FILTER_EXTENSION;
columnFilterManager = new ColumnFilterManager<VTMatch>(matchesTable, matchesTableModel,
preferenceKey, this::updateColumnFilter);
vtColumnFilter = new VTColumnFilter(columnFilterManager.getCurrentFilter());
addFilter(vtColumnFilter);
return columnFilterManager.getConfigureButton();
}
private void updateColumnFilter() {
vtColumnFilter.setFilter(columnFilterManager.getCurrentFilter());
refilter();
}
private JComponent createTextFilterPanel() {
// MatchNameFilter nameFilterPanel = new MatchNameFilter(controller, matchesTable);
AllTextFilter<VTMatch> allTextFilter =
new AllTextFilter<>(controller, matchesTable, matchesTableModel);
allTextFilter.setName(TEXT_FILTER_NAME);
@ -506,6 +530,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
}
ancillaryFilterDialog.dispose();
columnFilterManager.dispose();
}
@Override
@ -1069,4 +1095,73 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
return list;
}
}
private class VTColumnFilter extends Filter<VTMatch> {
private ColumnBasedTableFilter<VTMatch> columnFilter;
VTColumnFilter(ColumnBasedTableFilter<VTMatch> columnFilter) {
this.columnFilter = columnFilter;
}
void setFilter(ColumnBasedTableFilter<VTMatch> columnFilter) {
this.columnFilter = columnFilter;
}
@Override
public boolean passesFilter(VTMatch t) {
if (columnFilter == null) {
return true;
}
return columnFilter.acceptsRow(t);
}
@Override
public FilterEditingStatus getFilterStatus() {
if (columnFilter == null || columnFilter.isEmpty()) {
return FilterEditingStatus.NONE;
}
return FilterEditingStatus.APPLIED;
}
@Override
public JComponent getComponent() {
// This filter is configured outside of the VT filter API
throw new UnsupportedOperationException();
}
@Override
public FilterShortcutState getFilterShortcutState() {
return FilterShortcutState.REQUIRES_CHECK;
}
@Override
public Filter<VTMatch> createCopy() {
return this; // does not currently support copying; should not be needed
}
@Override
public void readConfigState(SaveState saveState) {
// handled by the column filter manager
}
@Override
public void writeConfigState(SaveState saveState) {
// handled by the column filter manager
}
@Override
public boolean isSubFilterOf(Filter<VTMatch> otherFilter) {
if (columnFilter == null) {
return false;
}
if (otherFilter instanceof VTColumnFilter otherColumnFilter) {
return columnFilter.isSubFilterOf(otherColumnFilter.columnFilter);
}
return false;
}
}
}

View file

@ -15,11 +15,13 @@
*/
package ghidra.feature.vt.gui.task;
import java.util.List;
import java.util.*;
import docking.widgets.OptionDialog;
import ghidra.feature.vt.api.db.VTMatchSetDB;
import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -38,26 +40,75 @@ public class RemoveMatchTask extends VtTask {
return true;
}
private boolean removeMatches(TaskMonitor monitor) throws CancelledException {
private void removeMatches(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Removing matches");
monitor.initialize(matches.size());
boolean failed = false;
for (VTMatch match : matches) {
int n = matches.size();
monitor.initialize(n);
//
// First remove all matches that will not require user prompting (those that are not
// accepted or they are not the last match for a shared association).
//
List<VTMatch> list = new ArrayList<>(matches); // create a mutable list
Iterator<VTMatch> it = list.iterator();
while (it.hasNext()) {
monitor.checkCancelled();
VTMatch match = it.next();
VTMatchSetDB matchSet = (VTMatchSetDB) match.getMatchSet();
boolean matchRemoved = matchSet.removeMatch(match);
if (!matchRemoved) {
failed = true;
if (matchSet.removeMatch(match)) {
it.remove();
}
monitor.incrementProgress(1);
}
monitor.setProgress(matches.size());
if (failed) {
reportError("One or more of your matches could not be removed." +
"\nNote: You can't remove a match if it is currently accepted.");
if (list.isEmpty()) {
return;
}
//
// Now we have to ask the user if they wish to remove applied matches.
//
int delta = n - list.size();
//@formatter:off
String message = """
Deleted %d of %d matches.
The remaining %d matches are ACCEPTED. Do you wish to delete these matches and
leave any applied destination program markup in place?
(Press F1 to see more help details)
""".formatted(delta, n, list.size());
//@formatter:on
RemoveMatchDialog dialog = new RemoveMatchDialog(message);
if (!dialog.promptToDelete()) {
return;
}
it = list.iterator();
while (it.hasNext()) {
monitor.checkCancelled();
VTMatch match = it.next();
VTMatchSetDB matchSet = (VTMatchSetDB) match.getMatchSet();
matchSet.deleteMatch(match);
it.remove();
monitor.incrementProgress(1);
}
return true;
}
private class RemoveMatchDialog extends OptionDialog {
RemoveMatchDialog(String message) {
super("Delete ACCEPTED Matches?", message, "Delete Accepted Matches", "Finish",
OptionDialog.QUESTION_MESSAGE, null, false);
setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Remove_Match"));
}
boolean promptToDelete() {
int choice = super.show();
return choice == OptionDialog.OPTION_ONE; // "Delete Accepted Matches"
}
}
}

View file

@ -125,11 +125,6 @@ public abstract class AbstractTextFilter<T> extends Filter<T> {
return component;
}
@Override
public void clearFilter() {
textField.setText(defaultValue);
}
@Override
public FilterEditingStatus getFilterStatus() {
return textField.getFilterStatus();

View file

@ -115,7 +115,7 @@ public class ImpliedMatchUtils {
VTMatchSet impliedMatchSet = session.getImpliedMatchSet();
for (VTMatch vtMatch : matches) {
if (vtMatch.getMatchSet() == impliedMatchSet) {
impliedMatchSet.removeMatch(vtMatch);
impliedMatchSet.deleteMatch(vtMatch);
}
}
}
@ -123,8 +123,7 @@ public class ImpliedMatchUtils {
/**
* Method for finding version tracking implied matches given an accepted matched
* function. Each referenced data and function that exist in equivalent sections
* of the matched source and destination functions will added to the current
* version tracking session as an implied match.
* of the matched source and destination functions will be returned in the given set.
*
* @param sourceFunction The matched function from the source program
* @param destinationFunction The matched function from the destination program

View file

@ -18,7 +18,7 @@ package ghidra.feature.vt.api;
import static ghidra.feature.vt.db.VTTestUtils.*;
import static org.junit.Assert.*;
import java.util.*;
import java.util.Arrays;
import org.junit.*;
@ -27,9 +27,8 @@ import ghidra.feature.vt.api.main.VTAssociationStatus;
import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.task.AcceptMatchTask;
import ghidra.feature.vt.gui.task.VtTask;
import ghidra.feature.vt.gui.util.VTOptionDefines;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
@ -39,10 +38,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.test.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
@ -53,13 +49,8 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
private ProgramDB sourceProgram;
private ProgramDB destinationProgram;
private VTPlugin plugin;
private DomainObjectListenerRecorder eventRecorder = new DomainObjectListenerRecorder();
private Options options;
public VTMatchAcceptTest() {
super();
}
@Before
public void setUp() throws Exception {
@ -70,7 +61,6 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
ClassicSampleX86ProgramBuilder destinationBuilder = new ClassicSampleX86ProgramBuilder();
destinationProgram = destinationBuilder.getProgram();
destinationProgram.addListener(eventRecorder);
tool = env.getTool();
@ -84,29 +74,21 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
runSwing(() -> controller.openVersionTrackingSession(session));
options = controller.getOptions();
options.setBoolean(VTOptionDefines.AUTO_CREATE_IMPLIED_MATCH, false);
options.setBoolean(VTOptionDefines.APPLY_FUNCTION_NAME_ON_ACCEPT, false);
options.setBoolean(VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT, false);
}
@After
public void tearDown() throws Exception {
waitForBusyTool(tool);
destinationProgram.flushEvents();
waitForSwing();
env.dispose();
}
@Test
public void testAcceptWithApplyDataLabels() throws Exception {
//
// BTW this test exposes a bug because the hook that runs when you apply data on accept was.
// in a side effect, causing the destination address to be set. When the hook was changed to
// not set the destination address, the accept task was not setting the destination address
// as it should.
// This test exposes a bug because the hook that runs when you apply data on accept was
// exhibiting a side effect, causing the destination address to be set. When the hook was
// changed to not set the destination address, the accept task was not setting the
// destination address as it should.
//
options.setBoolean(VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT, true);
@ -147,11 +129,9 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
}
}
private void runTask(Task task) throws CancelledException {
task.run(TaskMonitor.DUMMY);
destinationProgram.flushEvents();
waitForSwing();
private void runTask(VtTask task) {
controller.runVTTask(task);
waitForProgram(destinationProgram);
}
private Data setData(DataType dataType, int dtLength, Address address, Program program)
@ -170,14 +150,4 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
}
return data;
}
private class DomainObjectListenerRecorder implements DomainObjectListener {
List<DomainObjectChangedEvent> events = new ArrayList<DomainObjectChangedEvent>();
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
events.add(ev);
}
}
}

View file

@ -69,7 +69,6 @@ public class VTMatchApplyTest extends AbstractGhidraHeadedIntegrationTest {
private ProgramDB destinationProgram;
private VTPlugin plugin;
// TODO: debug
private DomainObjectListenerRecorder eventRecorder = new DomainObjectListenerRecorder();
@Before

View file

@ -0,0 +1,354 @@
/* ###
* 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;
import static ghidra.feature.vt.db.VTTestUtils.*;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.*;
import docking.DialogComponentProvider;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.db.DummyTestProgramCorrelator;
import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.task.*;
import ghidra.feature.vt.gui.util.VTOptionDefines;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.test.*;
public class VTMatchRemoveTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private VTController controller;
private VTPlugin plugin;
private VTSessionDB session;
private ProgramDB srcProgram;
private ProgramDB destProgram;
@Before
public void setUp() throws Exception {
env = new TestEnv();
ClassicSampleX86ProgramBuilder sourceBuilder = new ClassicSampleX86ProgramBuilder();
srcProgram = sourceBuilder.getProgram();
ClassicSampleX86ProgramBuilder destinationBuilder = new ClassicSampleX86ProgramBuilder();
destProgram = destinationBuilder.getProgram();
tool = env.getTool();
tool.addPlugin(VTPlugin.class.getName());
plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
srcProgram, destProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session));
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testRemoveMatch_UnaccpetedMatch() throws Exception {
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match, false);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertNoLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_AccpetedMatch() throws Exception {
/*
Test:
- create and apply a match
- remove the match
- leave the applied markup after match removal
*/
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
setApplyDataLabelOnAccept();
accept(match);
assertAcceptedAndLabelApplied(match, labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_Accepted_MultipleMatchesForAssociation() throws Exception {
/*
Test:
- create multiple matches for the same association
- apply one match
- remove the applied match
- leave the applied markup after match removal
*This tests control flow that avoids execution when the match being removed is the last
match for an association.
*/
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithMultipleMatchesToSameAssociation(srcAddr, destAddr);
setApplyDataLabelOnAccept();
accept(match);
assertAcceptedAndLabelApplied(match, labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match, false);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_RejectedMatch() throws Exception {
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
setApplyDataLabelOnAccept();
reject(match);
assertNoLabelApplied(labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match, false);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertNoLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_AccpetedMatch_ChooseNotToDelete() throws Exception {
/*
Test:
- create and apply a match
- remove the match, but cancel at dialog prompt
- match should still be valid; markup should still be applied
*/
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
setApplyDataLabelOnAccept();
accept(match);
assertAcceptedAndLabelApplied(match, labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
startRemoveThenCancel(match);
assertMatchNotRemoved(matchSet, srcAddr, destAddr);
assertLabelApplied(labelName, destAddr);
}
//=================================================================================================
// Private Methods
//=================================================================================================
private void remove(VTMatch match) {
remove(match, true);
}
private void remove(VTMatch match, boolean expectPrompt) {
RemoveMatchTask task = new RemoveMatchTask(session, List.of(match));
AtomicBoolean finished = runTaskLater(task); // this task is blocking, so run later and wait
if (expectPrompt) {
DialogComponentProvider removeDialog =
waitForDialogComponent("Delete ACCEPTED Matches?");
pressButtonByText(removeDialog, "Delete Accepted Matches");
}
// let the task finish processing after pressing the button
waitFor(finished);
waitForProgram(destProgram);
}
private void startRemoveThenCancel(VTMatch match) {
RemoveMatchTask task = new RemoveMatchTask(session, List.of(match));
AtomicBoolean finished = runTaskLater(task); // this task is blocking, so run later and wait
DialogComponentProvider removeDialog = waitForDialogComponent("Delete ACCEPTED Matches?");
pressButtonByText(removeDialog, "Finish");
// let the task finish processing after pressing the button
waitFor(finished);
waitForProgram(destProgram);
}
private void assertLabelApplied(String labelName, Address addr) {
assertEquals(labelName, getSymbol(destProgram, addr).getName());
}
private void assertNoLabelApplied(String labelName, Address addr) {
Symbol symbol = getSymbol(destProgram, addr);
if (symbol == null) {
return; // no label; expected
}
assertNotEquals(labelName, symbol.getName());
}
private void assertMatchRemoved(VTMatchSet matchSet, Address srcAddr, Address destAddr) {
Collection<VTMatch> matches = matchSet.getMatches(srcAddr, destAddr);
assertTrue(matches.isEmpty());
}
private void assertMatchNotRemoved(VTMatchSet matchSet, Address srcAddr, Address destAddr) {
Collection<VTMatch> matches = matchSet.getMatches(srcAddr, destAddr);
assertFalse(matches.isEmpty());
}
private void assertAcceptedAndLabelApplied(VTMatch match, String labelName, Address addr) {
VTAssociationStatus status = match.getAssociation().getStatus();
assertEquals(VTAssociationStatus.ACCEPTED, status);
assertEquals(labelName, getSymbol(destProgram, addr).getName());
}
private Symbol getSymbol(Program p, Address addr) {
return p.getSymbolTable().getPrimarySymbol(addr);
}
private void setApplyDataLabelOnAccept() {
ToolOptions options = controller.getOptions();
options.setBoolean(VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT, true);
}
private void accept(VTMatch match) throws Exception {
AcceptMatchTask task = new AcceptMatchTask(controller, List.of(match));
runTask(task);
}
private void reject(VTMatch match) throws Exception {
RejectMatchTask task = new RejectMatchTask(session, List.of(match));
runTask(task);
}
private void setDataOnPrograms(Address srcAddr, Address destAddr) {
DataType srcDt = new DWordDataType();
DataType destDt1 = new StringDataType();
DataType destDt2 = new WordDataType();
setData(srcDt, 4, srcAddr, srcProgram);
setData(destDt1, 2, destAddr, destProgram);
setData(destDt2, 2, destAddr.add(2), destProgram);
}
private Symbol addLabel(String name, Address address, Program program) {
return tx(program, () -> {
SymbolTable symbolTable = program.getSymbolTable();
return symbolTable.createLabel(address, name, SourceType.USER_DEFINED);
});
}
private Data setData(DataType dataType, int length, Address address, Program program) {
return tx(program, () -> {
Listing listing = program.getListing();
return listing.createData(address, dataType, length);
});
}
private AtomicBoolean runTaskLater(VtTask task) {
AtomicBoolean finishedFlag = new AtomicBoolean();
runSwingLater(() -> {
controller.runVTTask(task);
finishedFlag.set(true);
});
waitForSwing();
return finishedFlag;
}
private void runTask(VtTask task) {
controller.runVTTask(task);
waitForProgram(destProgram);
}
private VTMatch createMatchSetWithMultipleMatchesToSameAssociation(Address srcAddr,
Address destAddr) throws Exception {
int txId = 0;
try {
txId = session.startTransaction("Test Create Data Match Set");
VTMatchInfo info = createRandomMatch(srcAddr, destAddr, session);
info.setAssociationType(VTAssociationType.DATA);
VTMatchSet matchSet =
session.createMatchSet(createProgramCorrelator(srcProgram, destProgram));
VTMatch firstMatch = matchSet.addMatch(info);
// create a second match, match set and correlator, all tied to the given association
DummyTestProgramCorrelator pc2 =
(DummyTestProgramCorrelator) createProgramCorrelator(srcProgram, destProgram);
pc2.setName("Correlator Two");
VTMatchSet ms2 = session.createMatchSet(pc2);
ms2.addMatch(createRandomMatch(srcAddr, destAddr, session));
return firstMatch;
}
finally {
session.endTransaction(txId, true);
}
}
}

View file

@ -27,6 +27,7 @@ import ghidra.util.task.TaskMonitor;
public class DummyTestProgramCorrelator extends VTAbstractProgramCorrelator {
private String name = "DummyTestProgramCorrelator";
private int matchCount = 1;
public DummyTestProgramCorrelator() {
@ -84,8 +85,12 @@ public class DummyTestProgramCorrelator extends VTAbstractProgramCorrelator {
}
}
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return "DummyTestProgramCorrelator";
return name;
}
}

View file

@ -119,6 +119,7 @@ public class VTDomainObjectEventsTest extends VTBaseTestCase {
assertEquals(VTEvent.MATCH_ADDED, events.get(0).getEventType());
}
@SuppressWarnings("removal") // ignore the warning until removeMatch() is removed
@Test
public void testEventsForRemovingLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
@ -134,6 +135,22 @@ public class VTDomainObjectEventsTest extends VTBaseTestCase {
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@Test
public void testEventsForDeletingLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
clearEvents();
VTMatchInfo matchInfo = VTTestUtils.createRandomMatch(null);
VTMatch match = manualMatchSet.addMatch(matchInfo);
clearEvents();
manualMatchSet.deleteMatch(match);
assertEventCount(2);
assertEquals(VTEvent.ASSOCIATION_REMOVED, events.get(0).getEventType());
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@SuppressWarnings("removal") // ignore the warning until removeMatch() is removed
@Test
public void testEventsForRemovingNonLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
@ -149,6 +166,21 @@ public class VTDomainObjectEventsTest extends VTBaseTestCase {
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@Test
public void testEventsForDeletingNonLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
clearEvents();
VTMatchInfo matchInfo = VTTestUtils.createRandomMatch(null);
VTMatch match = manualMatchSet.addMatch(matchInfo);
clearEvents();
manualMatchSet.deleteMatch(match);
assertEventCount(2);
assertEquals(VTEvent.ASSOCIATION_REMOVED, events.get(0).getEventType());
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@Test
public void testEventsForRejectingMatch() throws VTAssociationStatusException {
VTMatchSet matchSet = createMatchSet();