Merge remote-tracking branch

'origin/GP-989-dragonmacher-bookmark-table-delete' into Ghidra_10.0
(closes #3066)
This commit is contained in:
ghidravore
2021-06-01 16:08:41 -04:00
2 changed files with 85 additions and 20 deletions
@@ -111,7 +111,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
BookmarkType[] types = bmMgr.getBookmarkTypes(); BookmarkType[] types = bmMgr.getBookmarkTypes();
assertEquals(8, types.length); assertEquals(8, types.length);
checkBookmarkList(bookmarks); checkBookmarkTable(bookmarks);
DockingActionIf pa = getAction(plugin, "Filter Bookmarks"); DockingActionIf pa = getAction(plugin, "Filter Bookmarks");
performAction(pa, false); performAction(pa, false);
@@ -123,7 +123,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
}); });
Bookmark[] bms = new Bookmark[] { bookmarks[6], bookmarks[7], bookmarks[8] }; Bookmark[] bms = new Bookmark[] { bookmarks[6], bookmarks[7], bookmarks[8] };
checkBookmarkList(bms); checkBookmarkTable(bms);
} }
@Test @Test
@@ -131,7 +131,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
showBookmarkProvider(); showBookmarkProvider();
checkBookmarkList(bookmarks); checkBookmarkTable(bookmarks);
BookmarkTableModel model = (BookmarkTableModel) table.getModel(); BookmarkTableModel model = (BookmarkTableModel) table.getModel();
DefaultTableTextFilterFactory<BookmarkRowObject> factory = DefaultTableTextFilterFactory<BookmarkRowObject> factory =
new DefaultTableTextFilterFactory<>(new FilterOptions()); new DefaultTableTextFilterFactory<>(new FilterOptions());
@@ -141,16 +141,16 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
runSwing(() -> model.setTableFilter(factory.getTableFilter("help", transformer))); runSwing(() -> model.setTableFilter(factory.getTableFilter("help", transformer)));
Bookmark[] bms = new Bookmark[] {}; Bookmark[] bms = new Bookmark[] {};
checkBookmarkList(bms); checkBookmarkTable(bms);
runSwing(() -> model.setTableFilter(factory.getTableFilter("Test20", transformer))); runSwing(() -> model.setTableFilter(factory.getTableFilter("Test20", transformer)));
bms = new Bookmark[] { bookmarks[1], bookmarks[2] }; bms = new Bookmark[] { bookmarks[1], bookmarks[2] };
checkBookmarkList(bms); checkBookmarkTable(bms);
runSwing(() -> model.setTableFilter(null)); runSwing(() -> model.setTableFilter(null));
checkBookmarkList(bookmarks); checkBookmarkTable(bookmarks);
} }
@Test @Test
@@ -332,7 +332,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
Bookmark[] bms = new Bookmark[] { bookmarks[0], bookmarks[1], bookmarks[3], bookmarks[4], Bookmark[] bms = new Bookmark[] { bookmarks[0], bookmarks[1], bookmarks[3], bookmarks[4],
bookmarks[5], bookmarks[6], bookmarks[7], bookmarks[8] }; bookmarks[5], bookmarks[6], bookmarks[7], bookmarks[8] };
checkBookmarkList(bms); checkBookmarkTable(bms);
selectRow(2, false); selectRow(2, false);
selectRow(5, true); selectRow(5, true);
@@ -340,7 +340,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
performAction(deleteAction, true); performAction(deleteAction, true);
bms = new Bookmark[] { bookmarks[0], bookmarks[1], bookmarks[4], bookmarks[5], bookmarks[7], bms = new Bookmark[] { bookmarks[0], bookmarks[1], bookmarks[4], bookmarks[5], bookmarks[7],
bookmarks[8] }; bookmarks[8] };
checkBookmarkList(bms); checkBookmarkTable(bms);
} }
@@ -395,7 +395,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddBookmark() throws Exception { public void testAddBookmark() throws Exception {
showBookmarkProvider(); showBookmarkProvider();
checkBookmarkList(bookmarks); checkBookmarkTable(bookmarks);
DockingActionIf pa = getAction(plugin, "Add Bookmark"); DockingActionIf pa = getAction(plugin, "Add Bookmark");
performAction(pa, codeViewerProvider, false); performAction(pa, codeViewerProvider, false);
@@ -417,13 +417,13 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
Bookmark[] bks = new Bookmark[10]; Bookmark[] bks = new Bookmark[10];
list.toArray(bks); list.toArray(bks);
Arrays.sort(bks); Arrays.sort(bks);
checkBookmarkList(bks); checkBookmarkTable(bks);
undo(program); undo(program);
checkBookmarkList(bookmarks); checkBookmarkTable(bookmarks);
redo(program); redo(program);
checkBookmarkList(bks); checkBookmarkTable(bks);
} }
@Test @Test
@@ -446,6 +446,37 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertFalse(list.contains(bookmark)); assertFalse(list.contains(bookmark));
} }
@Test
public void testDeleteBookmarkUpdatesTable() throws Exception {
//
// This test catches a bug with removing a deleted bookmark from the table. The bug was
// triggered by a failure in the binary search. Specifically, if the binary search does
// not touch the item to be deleted, then the item would not get removed from the table.
//
showBookmarkProvider();
List<Bookmark> list = getBookmarks(program.getBookmarkManager());
int n = list.size();
int rowCount = n;
for (int i = 0; i < n; i++) {
// cannot merely delete from the beginning to trigger this bug
int index = getRandomInt(0, list.size() - 1);
Bookmark bookmark = list.get(index);
Address address = bookmark.getAddress();
DeleteBookmarkAction action = new DeleteBookmarkAction(plugin, bookmark, true);
MarkerLocation markerLocation = new MarkerLocation(null, program, address, 0, 0);
performAction(action, createContext(markerLocation), true);
waitForTable();
assertEquals(rowCount - 1, table.getRowCount());
list = getBookmarks(program.getBookmarkManager());
rowCount = list.size();
}
}
@Test @Test
public void testMultipleDelete() throws Exception { public void testMultipleDelete() throws Exception {
showBookmarkProvider(); showBookmarkProvider();
@@ -739,7 +770,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForTableModel((ThreadedTableModel<Bookmark, ?>) table.getModel()); waitForTableModel((ThreadedTableModel<Bookmark, ?>) table.getModel());
} }
private void checkBookmarkList(Bookmark[] bookmarksToCheck) { private void checkBookmarkTable(Bookmark[] bookmarksToCheck) {
waitForBusyTool(tool); waitForBusyTool(tool);
waitForTable(); waitForTable();
@@ -15,9 +15,10 @@
*/ */
package docking.widgets.table.threaded; package docking.widgets.table.threaded;
import java.util.List; import java.util.*;
import docking.widgets.table.AddRemoveListItem; import docking.widgets.table.AddRemoveListItem;
import docking.widgets.table.TableSortingContext;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -25,37 +26,70 @@ import ghidra.util.task.TaskMonitor;
* A strategy that uses the table's sort state to perform a binary search of items to be added * A strategy that uses the table's sort state to perform a binary search of items to be added
* and removed. * and removed.
* *
* <P>This strategy is aware that some items may not be correctly removed, such as database items
* that have been deleted externally to the table. These items may require a brute force update
* to achieve removal.
*
* @param <T> the row type * @param <T> the row type
*/ */
public class DefaultAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> { public class DefaultAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> {
@Override @Override
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> updatedData, public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> tableData,
TaskMonitor monitor) throws CancelledException { TaskMonitor monitor) throws CancelledException {
int n = addRemoveList.size(); int n = addRemoveList.size();
monitor.setMessage("Adding/Removing " + n + " items..."); monitor.setMessage("Adding/Removing " + n + " items...");
monitor.initialize(n); monitor.initialize(n);
// Note: this class does not directly perform a binary such, but instead relies on that Set<T> failedToRemove = new HashSet<>();
// Note: this class does not directly perform a binary search, but instead relies on that
// work to be done by the call to TableData.remove() // work to be done by the call to TableData.remove()
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
AddRemoveListItem<T> item = addRemoveList.get(i); AddRemoveListItem<T> item = addRemoveList.get(i);
T value = item.getValue(); T value = item.getValue();
if (item.isChange()) { if (item.isChange()) {
updatedData.remove(value); tableData.remove(value);
updatedData.insert(value); tableData.insert(value);
} }
else if (item.isRemove()) { else if (item.isRemove()) {
updatedData.remove(value); if (!tableData.remove(value)) {
failedToRemove.add(value);
}
} }
else if (item.isAdd()) { else if (item.isAdd()) {
updatedData.insert(value); tableData.insert(value);
} }
monitor.checkCanceled(); monitor.checkCanceled();
monitor.setProgress(i); monitor.setProgress(i);
} }
if (!failedToRemove.isEmpty()) {
int size = failedToRemove.size();
String message = size == 1 ? "1 deleted item..." : size + " deleted items...";
monitor.setMessage("Removing " + message);
tableData.process((data, sortContext) -> {
return expungeLostItems(failedToRemove, data, sortContext);
});
}
monitor.setMessage("Done adding/removing"); monitor.setMessage("Done adding/removing");
} }
private List<T> expungeLostItems(Set<T> toRemove, List<T> data,
TableSortingContext<T> sortContext) {
List<T> newList = new ArrayList<>(data.size() - toRemove.size());
for (int i = 0; i < data.size(); i++) {
T rowObject = data.get(i);
if (!toRemove.contains(rowObject)) {
newList.add(rowObject);
}
}
return newList;
}
} }