Merge remote-tracking branch

'origin/GP-788-dragonmacher-symbol-table-ui-lockup--SQUASHED'

Conflicts:
	Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java
This commit is contained in:
ghidra1 2021-03-26 16:11:03 -04:00
commit ceb0b5ad27
10 changed files with 411 additions and 123 deletions

View file

@ -299,6 +299,10 @@ public class BookmarkPlugin extends ProgramPlugin
@Override @Override
public synchronized void domainObjectChanged(DomainObjectChangedEvent ev) { public synchronized void domainObjectChanged(DomainObjectChangedEvent ev) {
if (!provider.isVisible()) {
return;
}
if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) || if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_MOVED) || ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_MOVED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) { ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) {

View file

@ -106,7 +106,7 @@ public class AboutProgramPlugin extends Plugin implements FrontEndable {
ProgramActionContext pac = (ProgramActionContext) context; ProgramActionContext pac = (ProgramActionContext) context;
Program program = pac.getProgram(); Program program = pac.getProgram();
if (program != null) { if (program != null) {
String menuName = "About " + program.getDomainFile().getName() + "..."; String menuName = "About " + program.getDomainFile().getName();
getMenuBarData().setMenuItemName(menuName); getMenuBarData().setMenuItemName(menuName);
return true; return true;
} }

View file

@ -134,6 +134,10 @@ class ReferenceProvider extends ComponentProviderAdapter {
setVisible(true); setVisible(true);
} }
boolean isBusy() {
return referenceKeyModel.isBusy();
}
@Override @Override
public void componentHidden() { public void componentHidden() {
referenceKeyModel.setProgram(null); referenceKeyModel.setProgram(null);

View file

@ -151,8 +151,8 @@ class SymbolPanel extends JPanel {
filterDialog.adjustFilter(symProvider, tableModel); filterDialog.adjustFilter(symProvider, tableModel);
} }
NewSymbolFilter getFilter() { SymbolFilter getFilter() {
return filterDialog.getFilter(); return tableModel.getFilter();
} }
void readConfigState(SaveState saveState) { void readConfigState(SaveState saveState) {

View file

@ -149,10 +149,14 @@ class SymbolProvider extends ComponentProviderAdapter {
return symbolPanel.getTable(); return symbolPanel.getTable();
} }
NewSymbolFilter getFilter() { SymbolFilter getFilter() {
return symbolPanel.getFilter(); return symbolPanel.getFilter();
} }
boolean isShowingDynamicSymbols() {
return getFilter().acceptsDefaultLabelSymbols();
}
private String generateSubTitle() { private String generateSubTitle() {
SymbolFilter filter = symbolKeyModel.getFilter(); SymbolFilter filter = symbolKeyModel.getFilter();
int rowCount = symbolKeyModel.getRowCount(); int rowCount = symbolKeyModel.getRowCount();
@ -174,11 +178,15 @@ class SymbolProvider extends ComponentProviderAdapter {
} }
} }
boolean isBusy() {
return symbolKeyModel.isBusy();
}
@Override @Override
public void componentHidden() { public void componentHidden() {
symbolKeyModel.reload(null); symbolKeyModel.reload(null);
if (plugin != null) { if (plugin != null) {
plugin.closeReferenceProvider(); plugin.symbolProviderClosed();
} }
} }
@ -199,5 +207,4 @@ class SymbolProvider extends ComponentProviderAdapter {
void writeConfigState(SaveState saveState) { void writeConfigState(SaveState saveState) {
symbolPanel.writeConfigState(saveState); symbolPanel.writeConfigState(saveState);
} }
} }

View file

@ -71,7 +71,7 @@ public class SymbolReferenceModel extends AddressBasedTableModel<Reference> {
@Override @Override
protected TableColumnDescriptor<Reference> createTableColumnDescriptor() { protected TableColumnDescriptor<Reference> createTableColumnDescriptor() {
TableColumnDescriptor<Reference> descriptor = new TableColumnDescriptor<Reference>(); TableColumnDescriptor<Reference> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn( descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new ReferenceFromAddressTableColumn()), DiscoverableTableUtils.adaptColumForModel(this, new ReferenceFromAddressTableColumn()),
@ -139,7 +139,6 @@ public class SymbolReferenceModel extends AddressBasedTableModel<Reference> {
void symbolChanged(Symbol symbol) { void symbolChanged(Symbol symbol) {
if (currentSymbol != null && currentSymbol.equals(symbol)) { if (currentSymbol != null && currentSymbol.equals(symbol)) {
setCurrentSymbol(symbol);
return; return;
} }
checkRefs(symbol); checkRefs(symbol);

View file

@ -264,12 +264,13 @@ class SymbolTableModel extends AddressBasedTableModel<Symbol> {
updateObject(Symbol); updateObject(Symbol);
} }
else { else {
// the symbol may be in the table, as it could have passed the filter before the change
removeObject(Symbol); removeObject(Symbol);
} }
} }
void delete(List<Symbol> rowObjects) { void delete(List<Symbol> rowObjects) {
if (rowObjects == null || rowObjects.size() == 0) { if (rowObjects == null || rowObjects.isEmpty()) {
return; return;
} }
@ -530,7 +531,7 @@ class SymbolTableModel extends AddressBasedTableModel<Symbol> {
private class SourceTableColumn private class SourceTableColumn
extends AbstractProgramBasedDynamicTableColumn<Symbol, SourceType> { extends AbstractProgramBasedDynamicTableColumn<Symbol, SourceType> {
private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<SourceType>() { private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<>() {
@Override @Override
protected String getText(Object value) { protected String getText(Object value) {
if (value == null) { if (value == null) {

View file

@ -44,6 +44,9 @@ import ghidra.util.table.GhidraTable;
import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.SelectionNavigationAction;
import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.table.actions.MakeProgramSelectionAction;
import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.SwingUpdateManager;
import ghidra.util.task.TaskMonitor;
import ghidra.util.worker.Job;
import ghidra.util.worker.Worker;
import resources.Icons; import resources.Icons;
import resources.ResourceManager; import resources.ResourceManager;
@ -88,6 +91,13 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
private BlockModelService blockModelService; private BlockModelService blockModelService;
private SwingUpdateManager swingMgr; private SwingUpdateManager swingMgr;
/**
* A worker that will process domain object change event work off of the Swing thread. This
* solves the issue of db lock contention that can happen during analysis while this class
* attempts to get symbols from the db while processing a flurry of domain events.
*/
private Worker domainObjectWorker = Worker.createGuiWorker();
public SymbolTablePlugin(PluginTool tool) { public SymbolTablePlugin(PluginTool tool) {
super(tool); super(tool);
@ -124,6 +134,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
deleteAction.dispose(); deleteAction.dispose();
makeSelectionAction.dispose(); makeSelectionAction.dispose();
domainObjectWorker.dispose();
if (symProvider != null) { if (symProvider != null) {
symProvider.dispose(); symProvider.dispose();
symProvider = null; symProvider = null;
@ -165,17 +176,15 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
if (oldProg != null) { if (oldProg != null) {
inspector.setProgram(null); inspector.setProgram(null);
oldProg.removeListener(this); oldProg.removeListener(this);
domainObjectWorker.clearAllJobs();
symProvider.setProgram(null, inspector); symProvider.setProgram(null, inspector);
refProvider.setProgram(null, inspector); refProvider.setProgram(null, inspector);
tool.contextChanged(symProvider); tool.contextChanged(symProvider);
} }
currentProgram = newProg; currentProgram = newProg;
if (newProg != null) { if (newProg != null) {
currentProgram.addListener(this); currentProgram.addListener(this);
inspector.setProgram(currentProgram); inspector.setProgram(currentProgram);
symProvider.setProgram(currentProgram, inspector); symProvider.setProgram(currentProgram, inspector);
refProvider.setProgram(currentProgram, inspector); refProvider.setProgram(currentProgram, inspector);
} }
@ -184,17 +193,27 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
} }
} }
boolean isBusy() {
return domainObjectWorker.isBusy() || symProvider.isBusy() || refProvider.isBusy();
}
private void reload() {
domainObjectWorker.clearAllJobs();
symProvider.reload();
refProvider.reload();
}
@Override @Override
public void domainObjectChanged(DomainObjectChangedEvent ev) { public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (!symProvider.isVisible()) { if (!symProvider.isVisible() && !refProvider.isVisible()) {
return; return;
} }
if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) || if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_ADDED) || ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_ADDED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) { ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) {
symProvider.reload(); reload();
refProvider.reload();
return; return;
} }
@ -208,103 +227,74 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
} }
ProgramChangeRecord rec = (ProgramChangeRecord) doRecord; ProgramChangeRecord rec = (ProgramChangeRecord) doRecord;
Symbol symbol = null;
SymbolTable symbolTable = currentProgram.getSymbolTable();
switch (eventType) { switch (eventType) {
case ChangeManager.DOCR_CODE_ADDED: case ChangeManager.DOCR_CODE_ADDED:
case ChangeManager.DOCR_CODE_REMOVED: case ChangeManager.DOCR_CODE_REMOVED:
if (rec.getNewValue() instanceof Data) { if (rec.getNewValue() instanceof Data) {
symbol = symbolTable.getPrimarySymbol(rec.getStart()); domainObjectWorker.schedule(
if (symbol != null && symbol.isDynamic()) { new CodeAddedRemoveJob(currentProgram, rec.getStart()));
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
} }
break; break;
case ChangeManager.DOCR_SYMBOL_ADDED: case ChangeManager.DOCR_SYMBOL_ADDED:
Address addAddr = rec.getStart(); Address addAddr = rec.getStart();
Symbol primaryAtAdd = symbolTable.getPrimarySymbol(addAddr); Symbol symbol = (Symbol) rec.getNewValue();
if (primaryAtAdd != null && primaryAtAdd.isDynamic()) { domainObjectWorker.schedule(
symProvider.symbolRemoved(primaryAtAdd); new SymbolAddedJob(currentProgram, symbol, addAddr));
}
symbol = (Symbol) rec.getNewValue();
symProvider.symbolAdded(symbol);
refProvider.symbolAdded(symbol);
break; break;
case ChangeManager.DOCR_SYMBOL_REMOVED: case ChangeManager.DOCR_SYMBOL_REMOVED:
Address removeAddr = rec.getStart(); Address removeAddr = rec.getStart();
Long symbolID = (Long) rec.getNewValue(); Long symbolID = (Long) rec.getNewValue();
Symbol removedSymbol = domainObjectWorker.schedule(
symbolTable.createSymbolPlaceholder(removeAddr, symbolID); new SymbolRemovedJob(currentProgram, removeAddr, symbolID));
symProvider.symbolRemoved(removedSymbol);
refProvider.symbolRemoved(removedSymbol);
Symbol primaryAtRemove = symbolTable.getPrimarySymbol(removeAddr);
if (primaryAtRemove != null && primaryAtRemove.isDynamic()) {
symProvider.symbolAdded(primaryAtRemove);
}
break; break;
case ChangeManager.DOCR_SYMBOL_RENAMED: case ChangeManager.DOCR_SYMBOL_RENAMED:
case ChangeManager.DOCR_SYMBOL_SCOPE_CHANGED: case ChangeManager.DOCR_SYMBOL_SCOPE_CHANGED:
case ChangeManager.DOCR_SYMBOL_DATA_CHANGED: case ChangeManager.DOCR_SYMBOL_DATA_CHANGED:
symbol = (Symbol) rec.getObject(); symbol = (Symbol) rec.getObject();
if (!symbol.isDeleted()) { // symbol may have been removed (e.g., parameter) domainObjectWorker.schedule(new SymbolChangedJob(currentProgram, symbol));
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
break; break;
case ChangeManager.DOCR_SYMBOL_SOURCE_CHANGED: case ChangeManager.DOCR_SYMBOL_SOURCE_CHANGED:
symbol = (Symbol) rec.getObject(); symbol = (Symbol) rec.getObject();
symProvider.symbolChanged(symbol); domainObjectWorker.schedule(new SymbolSourceChangedJob(currentProgram, symbol));
break; break;
case ChangeManager.DOCR_SYMBOL_SET_AS_PRIMARY: case ChangeManager.DOCR_SYMBOL_SET_AS_PRIMARY:
symbol = (Symbol) rec.getNewValue(); symbol = (Symbol) rec.getNewValue();
symProvider.symbolChanged(symbol); Symbol oldPrimarySymbol = (Symbol) rec.getOldValue();
Symbol oldSymbol = (Symbol) rec.getOldValue(); domainObjectWorker.schedule(
if (oldSymbol != null) { new SymbolSetAsPrimaryJob(currentProgram, symbol, oldPrimarySymbol));
symProvider.symbolChanged(oldSymbol);
}
break; break;
case ChangeManager.DOCR_SYMBOL_ASSOCIATION_ADDED: case ChangeManager.DOCR_SYMBOL_ASSOCIATION_ADDED:
case ChangeManager.DOCR_SYMBOL_ASSOCIATION_REMOVED: case ChangeManager.DOCR_SYMBOL_ASSOCIATION_REMOVED:
break; break;
case ChangeManager.DOCR_MEM_REFERENCE_ADDED: case ChangeManager.DOCR_MEM_REFERENCE_ADDED:
Reference ref = (Reference) rec.getObject();
symbol = symbolTable.getSymbol(ref);
if (symbol != null) {
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
break;
case ChangeManager.DOCR_MEM_REFERENCE_REMOVED:
ref = (Reference) rec.getObject();
Address toAddr = ref.getToAddress();
if (toAddr.isMemoryAddress()) {
symbol = symbolTable.getSymbol(ref);
if (symbol == null) {
long id = symbolTable.getDynamicSymbolID(ref.getToAddress()); Reference ref = (Reference) rec.getObject();
removedSymbol = symbolTable.createSymbolPlaceholder(toAddr, id); domainObjectWorker.schedule(new ReferenceAddedJob(currentProgram, ref));
symProvider.symbolRemoved(removedSymbol); break;
}
else { case ChangeManager.DOCR_MEM_REFERENCE_REMOVED:
refProvider.symbolChanged(symbol);
} ref = (Reference) rec.getObject();
} domainObjectWorker.schedule(new ReferenceRemovedJob(currentProgram, ref));
break; break;
case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_ADDED: case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_ADDED:
case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_REMOVED: case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_REMOVED:
Symbol[] symbols = symbolTable.getSymbols(rec.getStart());
for (Symbol element : symbols) { Address address = rec.getStart();
symProvider.symbolChanged(element); domainObjectWorker.schedule(
refProvider.symbolChanged(element); new ExternalEntryChangedJob(currentProgram, address));
}
break; break;
} }
} }
@ -336,7 +326,8 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
} }
} }
void closeReferenceProvider() { void symbolProviderClosed() {
domainObjectWorker.clearAllJobs();
if (refProvider != null) { if (refProvider != null) {
refProvider.closeComponent(); refProvider.closeComponent();
} }
@ -514,4 +505,268 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
action.setSelected(false); action.setSelected(false);
action.setSelected(true); action.setSelected(true);
} }
//==================================================================================================
// Table Update Jobs
//==================================================================================================
private abstract class AbstractSymbolUpdateJob extends Job {
protected Program program;
AbstractSymbolUpdateJob(Program program) {
this.program = program;
}
@Override
public final void run(TaskMonitor taskMonitor) {
if (program != currentProgram) {
return;
}
doRun();
}
protected abstract void doRun();
}
private class CodeAddedRemoveJob extends AbstractSymbolUpdateJob {
private Address start;
CodeAddedRemoveJob(Program program, Address start) {
super(program);
this.start = start;
}
@Override
protected void doRun() {
if (!symProvider.isShowingDynamicSymbols()) {
return;
}
// Note: this code *should* be checking the entire address range to handle the case
// where large address range was cleared. This implementation will handle the
// case where individual code units are cleared. This feature has been this way
// for many years. The assumption is that most users are not showing dynamic
// symbols often, especially not when performing analysis or clearing large
// address ranges. Checking each address of the changed range is very slow.
// This code will need to be updated in the future if we decide updating the
// dynamic symbols in the symbol table is worth the cost. For now, if the table
// becomes out-of-date, then user can simply close and re-open the table to
// trigger an update.
SymbolTable symbolTable = currentProgram.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(start);
if (symbol != null && symbol.isDynamic()) {
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
}
}
private class SymbolAddedJob extends AbstractSymbolUpdateJob {
private Symbol symbol;
private Address address;
SymbolAddedJob(Program program, Symbol symbol, Address address) {
super(program);
this.symbol = symbol;
this.address = address;
}
@Override
protected void doRun() {
symProvider.symbolAdded(symbol);
refProvider.symbolAdded(symbol);
if (!symProvider.isShowingDynamicSymbols()) {
return;
}
SymbolTable symbolTable = program.getSymbolTable();
Symbol primaryAtAdd = symbolTable.getPrimarySymbol(address);
if (primaryAtAdd != null && primaryAtAdd.isDynamic()) {
symProvider.symbolRemoved(primaryAtAdd);
refProvider.symbolRemoved(primaryAtAdd);
}
}
}
private class SymbolRemovedJob extends AbstractSymbolUpdateJob {
private long symbolId;
private Address address;
SymbolRemovedJob(Program program, Address address, long symbolId) {
super(program);
this.address = address;
this.symbolId = symbolId;
}
@Override
protected void doRun() {
SymbolTable symbolTable = currentProgram.getSymbolTable();
Symbol removedSymbol =
symbolTable.createSymbolPlaceholder(address, symbolId);
symProvider.symbolRemoved(removedSymbol);
refProvider.symbolRemoved(removedSymbol);
if (!symProvider.isShowingDynamicSymbols()) {
return;
}
Symbol primaryAtRemove = symbolTable.getPrimarySymbol(address);
if (primaryAtRemove != null && primaryAtRemove.isDynamic()) {
symProvider.symbolAdded(primaryAtRemove);
refProvider.symbolAdded(primaryAtRemove);
}
}
}
private class SymbolChangedJob extends AbstractSymbolUpdateJob {
private Symbol symbol;
SymbolChangedJob(Program program, Symbol symbol) {
super(program);
this.symbol = symbol;
}
@Override
protected void doRun() {
// Note: should not need this check--the provider should be built to handle this
// if (symbol.checkIsValid())
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
}
private class SymbolSourceChangedJob extends AbstractSymbolUpdateJob {
private Symbol symbol;
SymbolSourceChangedJob(Program program, Symbol symbol) {
super(program);
this.symbol = symbol;
}
@Override
protected void doRun() {
symProvider.symbolChanged(symbol);
}
}
private class SymbolSetAsPrimaryJob extends AbstractSymbolUpdateJob {
private Symbol symbol;
private Symbol oldPrimarySymbol;
SymbolSetAsPrimaryJob(Program program, Symbol symbol, Symbol oldPrimarySymbol) {
super(program);
this.symbol = symbol;
this.oldPrimarySymbol = oldPrimarySymbol;
}
@Override
protected void doRun() {
symProvider.symbolChanged(symbol);
if (oldPrimarySymbol != null) {
symProvider.symbolChanged(oldPrimarySymbol);
}
}
}
private class ReferenceAddedJob extends AbstractSymbolUpdateJob {
private Reference reference;
ReferenceAddedJob(Program program, Reference reference) {
super(program);
this.reference = reference;
}
@Override
protected void doRun() {
Address toAddr = reference.getToAddress();
boolean isValid = toAddr.isMemoryAddress() || toAddr.isExternalAddress();
if (!isValid) {
return;
}
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getSymbol(reference);
if (symbol == null) {
return;
}
if (!symProvider.isShowingDynamicSymbols() && symbol.isDynamic()) {
return;
}
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
}
private class ReferenceRemovedJob extends AbstractSymbolUpdateJob {
private Reference reference;
ReferenceRemovedJob(Program program, Reference reference) {
super(program);
this.reference = reference;
}
@Override
protected void doRun() {
Address toAddr = reference.getToAddress();
boolean isValid = toAddr.isMemoryAddress() || toAddr.isExternalAddress();
if (!isValid) {
return;
}
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getSymbol(reference);
if (symbol != null) {
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
}
if (symProvider.isShowingDynamicSymbols()) {
long id = symbolTable.getDynamicSymbolID(reference.getToAddress());
Symbol removedSymbol = symbolTable.createSymbolPlaceholder(toAddr, id);
symProvider.symbolRemoved(removedSymbol);
refProvider.symbolRemoved(removedSymbol);
}
}
}
private class ExternalEntryChangedJob extends AbstractSymbolUpdateJob {
private Address address;
ExternalEntryChangedJob(Program program, Address address) {
super(program);
this.address = address;
}
@Override
protected void doRun() {
SymbolTable symbolTable = program.getSymbolTable();
Symbol[] symbols = symbolTable.getSymbols(address);
for (Symbol element : symbols) {
symProvider.symbolChanged(element);
refProvider.symbolChanged(element);
}
}
}
} }

View file

@ -37,7 +37,6 @@ import docking.action.DockingActionIf;
import docking.action.ToggleDockingAction; import docking.action.ToggleDockingAction;
import docking.widgets.filter.*; import docking.widgets.filter.*;
import docking.widgets.table.*; import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.app.cmd.label.AddLabelCmd; import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.label.CreateNamespacesCmd; import ghidra.app.cmd.label.CreateNamespacesCmd;
import ghidra.app.cmd.refs.RemoveReferenceCmd; import ghidra.app.cmd.refs.RemoveReferenceCmd;
@ -273,7 +272,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(filterDialog1, "OK"); pressButtonByText(filterDialog1, "OK");
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
// //
// Functions: 'ghidra', 'func_with_parms' // Functions: 'ghidra', 'func_with_parms'
@ -294,7 +293,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(filterDialog2, "OK"); pressButtonByText(filterDialog2, "OK");
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
// //
// Entry Points: 'entry' // Entry Points: 'entry'
@ -314,7 +313,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}); });
pressButtonByText(filterDialog3, "OK"); pressButtonByText(filterDialog3, "OK");
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
// //
// Locals: 'AnotherLocal', 'MyLocal' // Locals: 'AnotherLocal', 'MyLocal'
@ -348,7 +347,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
public void testEditing() throws Exception { public void testEditing() throws Exception {
openProgram("sample"); openProgram("sample");
waitForNotBusy(symbolTable); waitForNotBusy();
String symbolName = "ghidra"; String symbolName = "ghidra";
int row = findRow(symbolName); int row = findRow(symbolName);
@ -367,7 +366,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
myTypeText(editor, ".Is.Cool"); myTypeText(editor, ".Is.Cool");
runSwing(() -> symbolTable.editingStopped(new ChangeEvent(symbolTable))); runSwing(() -> symbolTable.editingStopped(new ChangeEvent(symbolTable)));
waitForNotBusy(symbolTable); waitForNotBusy();
assertTrue(!symbolTable.isEditing()); assertTrue(!symbolTable.isEditing());
@ -391,7 +390,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
st.createLabel(sample.getNewAddress(0x01008500), "abc123", SourceType.USER_DEFINED); st.createLabel(sample.getNewAddress(0x01008500), "abc123", SourceType.USER_DEFINED);
}); });
waitForNotBusy(symbolTable); waitForNotBusy();
int testTimeoutMs = 100; int testTimeoutMs = 100;
symbolTable.setAutoLookupTimeout(testTimeoutMs); symbolTable.setAutoLookupTimeout(testTimeoutMs);
@ -432,7 +431,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(deleteAction.isEnabled()); assertTrue(deleteAction.isEnabled());
performAction(deleteAction, true); performAction(deleteAction, true);
waitForNotBusy(symbolTable); waitForNotBusy();
assertNull(getUniqueSymbol(program, "ghidra")); assertNull(getUniqueSymbol(program, "ghidra"));
Symbol myLocalSymbol = getUniqueSymbol(program, "MyLocal"); Symbol myLocalSymbol = getUniqueSymbol(program, "MyLocal");
@ -462,7 +461,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertNull("Delete action did not delete symbol: " + anotherLocalSymbol, assertNull("Delete action did not delete symbol: " + anotherLocalSymbol,
anotherLocalSymbol);// AnotherLocal should have been promoted to global since user defined. anotherLocalSymbol);// AnotherLocal should have been promoted to global since user defined.
waitForNotBusy(symbolTable); waitForNotBusy();
// 1 Data label removed // 1 Data label removed
int expected = rowCount - 1; int expected = rowCount - 1;
@ -566,7 +565,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertFalse(clearPinnedAction.isEnabledForContext(actionContext)); assertFalse(clearPinnedAction.isEnabledForContext(actionContext));
performAction(setPinnedAction, actionContext, true); performAction(setPinnedAction, actionContext, true);
waitForNotBusy(symbolTable); waitForNotBusy();
for (int selectedRow : selectedRows) { for (int selectedRow : selectedRows) {
Symbol symbol = getSymbol(selectedRow); Symbol symbol = getSymbol(selectedRow);
assertTrue(symbol.isPinned()); assertTrue(symbol.isPinned());
@ -611,7 +610,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
return st.createLabel(sample.getNewAddress(0x01007000), "Zeus", return st.createLabel(sample.getNewAddress(0x01007000), "Zeus",
SourceType.USER_DEFINED); SourceType.USER_DEFINED);
}); });
waitForNotBusy(symbolTable); waitForNotBusy();
assertEquals(rowCount + 1, symbolTable.getRowCount()); assertEquals(rowCount + 1, symbolTable.getRowCount());
assertTrue(symbolModel.getRowIndex(sym) >= 0); assertTrue(symbolModel.getRowIndex(sym) >= 0);
@ -619,7 +618,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
return st.createLabel(sample.getNewAddress(0x01007100), "Athena", return st.createLabel(sample.getNewAddress(0x01007100), "Athena",
SourceType.USER_DEFINED); SourceType.USER_DEFINED);
}); });
waitForNotBusy(symbolTable); waitForNotBusy();
assertEquals(rowCount + 2, symbolTable.getRowCount()); assertEquals(rowCount + 2, symbolTable.getRowCount());
assertTrue(symbolModel.getRowIndex(sym) >= 0); assertTrue(symbolModel.getRowIndex(sym) >= 0);
} }
@ -648,7 +647,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
return st.createLabel(sample.getNewAddress(0x01007000), "saaaa", return st.createLabel(sample.getNewAddress(0x01007000), "saaaa",
SourceType.USER_DEFINED); SourceType.USER_DEFINED);
}); });
waitForNotBusy(symbolTable); waitForNotBusy();
assertTrue(symbolModel.getRowIndex(sym) >= 0); assertTrue(symbolModel.getRowIndex(sym) >= 0);
// make sure we added one while the filter is on // make sure we added one while the filter is on
@ -672,7 +671,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(filterDialog1, "OK"); pressButtonByText(filterDialog1, "OK");
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
// //
// Functions: 'ghidra', 'func_with_parms' // Functions: 'ghidra', 'func_with_parms'
@ -696,7 +695,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertNull(getUniqueSymbol(program, "EXT_00000051")); assertNull(getUniqueSymbol(program, "EXT_00000051"));
tx(program, () -> st.removeSymbolSpecial(sym)); tx(program, () -> st.removeSymbolSpecial(sym));
waitForNotBusy(symbolTable); waitForNotBusy();
// entry symbol replaced by dynamic External Entry symbol // entry symbol replaced by dynamic External Entry symbol
assertNull(getUniqueSymbol(program, "entry")); assertNull(getUniqueSymbol(program, "entry"));
@ -723,7 +722,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
rm.setPrimary(ref, true); rm.setPrimary(ref, true);
}); });
waitForNotBusy(symbolTable); waitForNotBusy();
row = symbolModel.getRowIndex(s); row = symbolModel.getRowIndex(s);
@ -752,7 +751,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
manager.delete(ref); manager.delete(ref);
}); });
waitForNotBusy(symbolTable); waitForNotBusy();
refCount = getRefCount(row); refCount = getRefCount(row);
assertNotNull(refCount); assertNotNull(refCount);
@ -782,19 +781,19 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
ClearCmd cmd = new ClearCmd(program.getMemory(), new ClearOptions()); ClearCmd cmd = new ClearCmd(program.getMemory(), new ClearOptions());
applyCmd(program, cmd); applyCmd(program, cmd);
waitForBusyTool(tool); waitForBusyTool(tool);
waitForNotBusy(symbolTable); waitForNotBusy();
// Externals are not cleared // Externals are not cleared
int clearedRowCount = 3; int clearedRowCount = 3;
assertEquals(clearedRowCount, symbolTable.getRowCount()); assertEquals(clearedRowCount, symbolTable.getRowCount());
undo(program); undo(program);
waitForNotBusy(symbolTable); waitForNotBusy();
assertEquals(startRowCount, symbolTable.getRowCount()); assertEquals(startRowCount, symbolTable.getRowCount());
redo(program); redo(program);
waitForNotBusy(symbolTable); waitForNotBusy();
assertEquals(clearedRowCount, symbolTable.getRowCount()); assertEquals(clearedRowCount, symbolTable.getRowCount());
} }
@ -815,7 +814,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(filterDialog, "OK"); pressButtonByText(filterDialog, "OK");
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
waitForSwing(); waitForSwing();
} }
@ -880,8 +879,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
performAction(refsToAction, true); performAction(refsToAction, true);
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
waitForNotBusy(referenceTable); waitForNotBusy();
assertEquals(4, referenceTable.getRowCount()); assertEquals(4, referenceTable.getRowCount());
assertReferencesAddressColumnValue(0, 0x54); assertReferencesAddressColumnValue(0, 0x54);
@ -899,8 +898,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
waitForNotBusy(referenceTable); waitForNotBusy();
assertEquals(3, referenceTable.getRowCount()); assertEquals(3, referenceTable.getRowCount());
assertReferencesAddressColumnValue(0, 0x53); assertReferencesAddressColumnValue(0, 0x53);
@ -917,8 +916,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForSwing(); waitForSwing();
waitForNotBusy(symbolTable); waitForNotBusy();
waitForNotBusy(referenceTable); waitForNotBusy();
assertEquals(2, referenceTable.getRowCount()); assertEquals(2, referenceTable.getRowCount());
// data // data
@ -1085,33 +1084,33 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
addLabel("bob", null, addr("010058f6")); addLabel("bob", null, addr("010058f6"));
addLabel("bob", "billy", addr("01005917")); addLabel("bob", "billy", addr("01005917"));
waitForNotBusy(symbolTable); waitForNotBusy();
int updatedRowCount = symbolTable.getRowCount(); int updatedRowCount = symbolTable.getRowCount();
assertEquals(rowCount + 2, updatedRowCount); assertEquals(rowCount + 2, updatedRowCount);
// test ascending // test ascending
runSwing(() -> TableUtils.columnSelected(symbolTable, 0)); runSwing(() -> TableUtils.columnSelected(symbolTable, 0));
waitForNotBusy(symbolTable); waitForNotBusy();
setFilterOptions(TextFilterStrategy.STARTS_WITH, false); setFilterOptions(TextFilterStrategy.STARTS_WITH, false);
myTypeText(textField, "bo"); myTypeText(textField, "bo");
waitForNotBusy(symbolTable); waitForNotBusy();
// make sure both 'bob's are in the table // make sure both 'bob's are in the table
assertEquals("Did not find two bobs.", 2, symbolTable.getRowCount()); assertEquals("Did not find two bobs.", 2, symbolTable.getRowCount());
modelMatchesIgnoringCase("bob"); modelMatchesIgnoringCase("bob");
myTypeText(textField, "b"); myTypeText(textField, "b");
waitForNotBusy(symbolTable); waitForNotBusy();
assertEquals("Did not find two bobs.", 2, symbolTable.getRowCount()); assertEquals("Did not find two bobs.", 2, symbolTable.getRowCount());
modelMatchesIgnoringCase("bob"); modelMatchesIgnoringCase("bob");
// test descending // test descending
runSwing(() -> TableUtils.columnSelected(symbolTable, 0)); runSwing(() -> TableUtils.columnSelected(symbolTable, 0));
waitForNotBusy(symbolTable); waitForNotBusy();
assertEquals("Did not find two bobs in descending order.", 2, symbolTable.getRowCount()); assertEquals("Did not find two bobs in descending order.", 2, symbolTable.getRowCount());
modelMatchesIgnoringCase("bob"); modelMatchesIgnoringCase("bob");
@ -1250,12 +1249,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
// use the version of triggerText that allows us to consume the event directly, bypassing // use the version of triggerText that allows us to consume the event directly, bypassing
// the focus system // the focus system
triggerText(symbolTable, text, consumer); triggerText(symbolTable, text, consumer);
waitForNotBusy(symbolTable); waitForNotBusy();
} }
private void setName(Symbol symbol, String name, SourceType type) throws Exception { private void setName(Symbol symbol, String name, SourceType type) throws Exception {
tx(program, () -> symbol.setName(name, SourceType.DEFAULT)); tx(program, () -> symbol.setName(name, SourceType.DEFAULT));
waitForNotBusy(symbolTable); waitForNotBusy();
} }
private Symbol getSymbol(int row) { private Symbol getSymbol(int row) {
@ -1315,7 +1314,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private void setFilterOptions(TextFilterStrategy filterStrategy, boolean caseSensitive) private void setFilterOptions(TextFilterStrategy filterStrategy, boolean caseSensitive)
throws Exception { throws Exception {
filterPanel.setFilterOptions(new FilterOptions(filterStrategy, true, caseSensitive, false)); filterPanel.setFilterOptions(new FilterOptions(filterStrategy, true, caseSensitive, false));
waitForNotBusy(symbolTable); waitForNotBusy();
} }
private JTextField getFilterTextField() { private JTextField getFilterTextField() {
@ -1351,7 +1350,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(filterDialog, "OK", true); pressButtonByText(filterDialog, "OK", true);
waitForNotBusy(symbolTable); waitForNotBusy();
} }
private int getRowForSymbol(Symbol symbol) { private int getRowForSymbol(Symbol symbol) {
@ -1390,7 +1389,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Command command = new AddLabelCmd(address, label, namespace, SourceType.USER_DEFINED); Command command = new AddLabelCmd(address, label, namespace, SourceType.USER_DEFINED);
tool.execute(command, program); tool.execute(command, program);
waitForNotBusy(symbolTable); waitForNotBusy();
} }
private Address addr(String address) { private Address addr(String address) {
@ -1399,7 +1398,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private void myTypeText(Component c, String text) throws Exception { private void myTypeText(Component c, String text) throws Exception {
triggerText(c, text); triggerText(c, text);
waitForNotBusy(symbolTable); waitForNotBusy();
} }
private void deleteTextFieldText(JTextField textField) { private void deleteTextFieldText(JTextField textField) {
@ -1410,7 +1409,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForSwing(); waitForSwing();
try { try {
waitForNotBusy(symbolTable); waitForNotBusy();
} }
catch (Exception exc) { catch (Exception exc) {
// we don't care // we don't care
@ -1419,7 +1418,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private void typeBackspaceOnComponent(Component component) throws Exception { private void typeBackspaceOnComponent(Component component) throws Exception {
triggerActionKey(component, 0, KeyEvent.VK_BACK_SPACE); triggerActionKey(component, 0, KeyEvent.VK_BACK_SPACE);
waitForNotBusy(symbolTable); waitForNotBusy();
} }
// makes sure that all of the symbols in the model start with the given string // makes sure that all of the symbols in the model start with the given string
@ -1459,10 +1458,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
private void waitForNotBusy(GTable table) throws Exception { private void waitForNotBusy() throws Exception {
waitForProgram(program); waitForProgram(program);
ThreadedTableModel<?, ?> model = (ThreadedTableModel<?, ?>) table.getModel(); waitForCondition(() -> !plugin.isBusy());
waitForTableModel(model);
} }
private void openProgram(String name) throws Exception { private void openProgram(String name) throws Exception {
@ -1583,9 +1581,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
filter.setFilter("Function Labels", true); filter.setFilter("Function Labels", true);
filter.setFilter("Default (Functions)", true); filter.setFilter("Default (Functions)", true);
filter.setFilter("Default (Labels)", true); filter.setFilter("Default (Labels)", true);
symbolModel.setFilter(filter); runSwing(() -> symbolModel.setFilter(filter));
waitForNotBusy(symbolTable); waitForNotBusy();
sortAscending(SymbolTableModel.LABEL_COL); sortAscending(SymbolTableModel.LABEL_COL);
} }

View file

@ -153,12 +153,32 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
return -1; return -1;
} }
boolean remove(ROW_OBJECT o) { boolean remove(ROW_OBJECT t) {
if (source != null) { if (source != null) {
source.remove(o); source.remove(t);
} }
return data.remove(o); if (sortContext.isUnsorted()) {
return data.remove(t); // no sort; cannot binary search
}
Comparator<ROW_OBJECT> comparator = sortContext.getComparator();
int index = Collections.binarySearch(data, t, comparator);
if (index > 0) {
data.remove(index);
return true;
}
//
// At this point we have one of 2 conditions: the object is not in the list, or the object
// does not work with the current sort comparator.
//
// There are cases where the comparator will not work for the object handed to this method,
// such as when some db objects get deleted and the client uses a proxy object to perform
// the delete. To handle these odd cases, we still have to brute-force search. If this
// proves to be a bottleneck, then we can update how we handle item removal.
//
return data.remove(t);
} }
/** /**