Merge remote-tracking branch 'origin/GP-2050-dragonmacher-symbol-table-update'

This commit is contained in:
Ryan Kurtz 2022-06-09 12:19:18 -04:00
commit 624a12b9e5
3 changed files with 8 additions and 856 deletions

View File

@ -1,270 +0,0 @@
/* ###
* 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.app.plugin.core.symtable;
import static docking.widgets.table.AddRemoveListItem.Type.*;
import java.util.*;
import docking.widgets.table.AddRemoveListItem;
import docking.widgets.table.TableSortingContext;
import docking.widgets.table.threaded.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* The symbol table users a {@link ThreadedTableModel}. This model does not correctly function
* with data that can change outside of the table. The symbol table's row objects are the
* {@link Symbol} db objects. These db objects can be changed by the user and by analysis
* while table is loaded. The problem with this is that the table's sort can be broken when
* symbols are to be added, removed or re-inserted, as this process requires a binary search which
* will be broken if the criteria used to sort the data has changed. Effectively, a symbol
* change can break the binary search if that symbol stays in a previously sorted position, but
* has updated data that would put the symbol in a new position if sorted again. For example,
* if the table is sorted on name and the name of a symbol changes, then future uses of the
* binary search will be broken while that symbol is still in the position that matches its old
* name.
* <p>
* This issue has been around for quite some time. To completely fix this issue, each row object
* of the symbol table would need to be immutable, at least on the sort criteria. We could fix
* this in the future if the *mostly correct* sorting behavior is not good enough. For now, the
* client can trigger a re-sort (e.g., by opening and closing the table) to fix the slightly
* out-of-sort data.
* <p>
* The likelihood of the sort being inconsistent now relates directly to how many changed symbols
* are in the table at the time of an insert. The more changes symbols, the higher the chance
* of a stale/misplaced symbol being used during a binary search, thus producing an invalid insert
* position.
* <p>
* This strategy is setup to mitigate the number of invalid symbols in the table at the
* time the inserts are applied. The basic workflow of this algorithm is:
* <pre>
* 1) condense the add / remove requests to remove duplicate efforts
* 2) process all removes first
* --all pure removes
* --all removes as part of a re-insert
* 3) process all items that failed to remove due to the sort data changing
* 4) process all adds (this step will fail if the data contains mis-sorted items)
* --all adds as part of a re-insert
* --all pure adds
* </pre>
*
* Step 3, processing failed removals, is done to avoid a brute force lookup at each removal
* request.
*
* <P>This strategy has knowledge of client proxy object usage. The proxy objects
* are coded such that the {@code hashCode()} and {@code equals()} methods will match those
* methods of the data's real objects.
*/
public class SymbolTableAddRemoveStrategy implements TableAddRemoveStrategy<SymbolRowObject> {
@Override
public void process(List<AddRemoveListItem<SymbolRowObject>> addRemoveList,
TableData<SymbolRowObject> tableData,
TaskMonitor monitor) throws CancelledException {
Set<AddRemoveListItem<SymbolRowObject>> items = coalesceAddRemoveItems(addRemoveList);
//
// Hash map the existing values so that we can use any object inside the add/remove list
// as a key into this map to get the matching existing value. Using the existing value
// enables the binary search to work when the add/remove item is a proxy object, but the
// existing item still has the data used to sort it. If the sort data has changed, then
// even this step will not allow the TableData to find the item in a search.
//
Map<SymbolRowObject, SymbolRowObject> hashed = new HashMap<>();
for (SymbolRowObject rowObject : tableData) {
hashed.put(rowObject, rowObject);
}
Set<SymbolRowObject> failedToRemove = new HashSet<>();
int n = items.size();
monitor.setMessage("Removing " + n + " items...");
monitor.initialize(n);
Iterator<AddRemoveListItem<SymbolRowObject>> it = items.iterator();
while (it.hasNext()) {
AddRemoveListItem<SymbolRowObject> item = it.next();
SymbolRowObject value = item.getValue();
if (item.isChange()) {
SymbolRowObject toRemove = hashed.remove(value);
remove(tableData, toRemove, failedToRemove);
monitor.incrementProgress(1);
}
else if (item.isRemove()) {
SymbolRowObject toRemove = hashed.remove(value);
remove(tableData, toRemove, failedToRemove);
it.remove();
}
monitor.checkCanceled();
}
if (!failedToRemove.isEmpty()) {
int size = failedToRemove.size();
String message = size == 1 ? "1 old symbol..." : size + " old symbols...";
monitor.setMessage("Removing " + message);
tableData.process((data, sortContext) -> {
return expungeLostItems(failedToRemove, data, sortContext);
});
}
n = items.size();
monitor.setMessage("Adding " + n + " items...");
it = items.iterator();
while (it.hasNext()) {
AddRemoveListItem<SymbolRowObject> item = it.next();
SymbolRowObject value = item.getValue();
if (item.isChange()) {
tableData.insert(value);
hashed.put(value, value);
}
else if (item.isAdd()) {
tableData.insert(value);
hashed.put(value, value);
}
monitor.checkCanceled();
monitor.incrementProgress(1);
}
monitor.setMessage("Done adding/removing");
}
private Set<AddRemoveListItem<SymbolRowObject>> coalesceAddRemoveItems(
List<AddRemoveListItem<SymbolRowObject>> addRemoveList) {
Map<Long, AddRemoveListItem<SymbolRowObject>> map = new HashMap<>();
for (AddRemoveListItem<SymbolRowObject> item : addRemoveList) {
if (item.isChange()) {
handleChange(item, map);
}
else if (item.isAdd()) {
handleAdd(item, map);
}
else {
handleRemove(item, map);
}
}
return new HashSet<>(map.values());
}
private void handleAdd(AddRemoveListItem<SymbolRowObject> item,
Map<Long, AddRemoveListItem<SymbolRowObject>> map) {
long id = item.getValue().getID();
AddRemoveListItem<SymbolRowObject> existing = map.get(id);
if (existing == null) {
map.put(id, item);
return;
}
if (existing.isChange()) {
return; // change -> add; keep the change
}
if (existing.isAdd()) {
return; // already an add
}
// remove -> add; make a change
map.put(id, new AddRemoveListItem<>(CHANGE, existing.getValue()));
}
private void handleRemove(AddRemoveListItem<SymbolRowObject> item,
Map<Long, AddRemoveListItem<SymbolRowObject>> map) {
long id = item.getValue().getID();
AddRemoveListItem<SymbolRowObject> existing = map.get(id);
if (existing == null) {
map.put(id, item);
return;
}
if (existing.isChange()) {
map.put(id, item); // change -> remove; just do the remove
return;
}
if (existing.isRemove()) {
return;
}
// add -> remove; do no work
map.remove(id);
}
private void handleChange(AddRemoveListItem<SymbolRowObject> item,
Map<Long, AddRemoveListItem<SymbolRowObject>> map) {
long id = item.getValue().getID();
AddRemoveListItem<SymbolRowObject> existing = map.get(id);
if (existing == null) {
map.put(id, item);
return;
}
if (!existing.isChange()) {
// either add or remove followed by a change; keep the change
map.put(id, item);
}
// otherwise, we had a change followed by a change; keep just 1 change
}
private void remove(TableData<SymbolRowObject> tableData, SymbolRowObject symbol,
Set<SymbolRowObject> failedToRemove) {
if (symbol == null) {
return;
}
if (!tableData.remove(symbol)) {
failedToRemove.add(symbol);
}
}
/*
* Removes the given set of items that were unsuccessfully removed from the table as part of
* the add/remove process. These items could not be removed because some part of their
* state has changed such that the binary search performed during the normal remove process
* cannot locate the item in the table data. This algorithm will check the given set of
* items against the entire list of table data, locating the item to be removed.
*/
private List<SymbolRowObject> expungeLostItems(Set<SymbolRowObject> toRemove,
List<SymbolRowObject> data,
TableSortingContext<SymbolRowObject> sortContext) {
if (sortContext.isUnsorted()) {
// this can happen if the data is unsorted and we were asked to remove an item that
// was never in the table for some reason
return data;
}
// Copy to a new list those items that are not marked for removal. This saves the
// list move its items every time a remove takes place
List<SymbolRowObject> newList = new ArrayList<>(data.size() - toRemove.size());
for (int i = 0; i < data.size(); i++) {
SymbolRowObject rowObject = data.get(i);
if (!toRemove.contains(rowObject)) {
newList.add(rowObject);
}
}
return newList;
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -18,7 +18,6 @@ package ghidra.app.plugin.core.symtable;
import java.util.*;
import docking.widgets.table.*;
import docking.widgets.table.threaded.TableAddRemoveStrategy;
import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd;
@ -61,9 +60,6 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
private SymbolRowObject lastSymbol;
private SymbolFilter filter;
private TableAddRemoveStrategy<SymbolRowObject> deletedDbObjectAddRemoveStrategy =
new SymbolTableAddRemoveStrategy();
SymbolTableModel(SymbolProvider provider, PluginTool tool) {
super("Symbols", tool, null, null);
this.provider = provider;
@ -91,11 +87,6 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
return descriptor;
}
@Override
protected TableAddRemoveStrategy<SymbolRowObject> getAddRemoveStrategy() {
return deletedDbObjectAddRemoveStrategy;
}
void setFilter(SymbolFilter filter) {
this.filter = filter;
reload();
@ -364,12 +355,12 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
protected Comparator<SymbolRowObject> createSortComparator(int columnIndex) {
DynamicTableColumn<SymbolRowObject, ?, ?> column = getColumn(columnIndex);
if (column instanceof NameTableColumn) {
// note: we use our own name comparator to increase sorting speed for the name
// column. This works because this comparator is called for each *row object*
// allowing the comparator to compare the Symbols based on name instead of
// note: we use our own name comparator to increase sorting speed for the name
// column. This works because this comparator is called for each *row object*
// allowing the comparator to compare the Symbols based on name instead of
// having to use the table model's code for getting a column value for the
// row object. The code for retrieving a column value is slower than just
// working with the row object directly. See
// working with the row object directly. See
// ThreadedTableModel.getCachedColumnValueForRow for more info.
return NAME_COL_COMPARATOR;
}
@ -436,16 +427,14 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public AddressBasedLocation getValue(SymbolRowObject rowObject, Settings settings,
Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
return getSymbolLocation(symbol);
}
@Override
public ProgramLocation getProgramLocation(SymbolRowObject rowObject, Settings settings,
Program p,
ServiceProvider svcProvider) {
Program p, ServiceProvider svcProvider) {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;

View File

@ -1,567 +0,0 @@
/* ###
* 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.app.plugin.core.symtable;
import static docking.widgets.table.AddRemoveListItem.Type.*;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.table.*;
import docking.widgets.table.threaded.TestTableData;
import ghidra.program.model.ProgramTestDouble;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.TestAddress;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.task.TaskMonitor;
public class SymbolTableAddRemoveStrategyTest {
private static Program DUMMY_PROGRAM = new ProgramTestDouble();
private SymbolTableAddRemoveStrategy strategy;
private SpyTableData spyTableData;
private List<SymbolRowObject> modelData;
@Before
public void setUp() throws Exception {
strategy = new SymbolTableAddRemoveStrategy();
modelData = createModelData();
spyTableData = createTableData();
}
private SpyTableData createTableData() {
Comparator<SymbolRowObject> comparator = (s1, s2) -> {
return s1.toString().compareTo(s2.toString()); // based on symbol name
};
TableSortState sortState = TableSortState.createDefaultSortState(0);
TableSortingContext<SymbolRowObject> sortContext =
new TableSortingContext<>(sortState, comparator);
modelData.sort(comparator);
return new SpyTableData(modelData, sortContext);
}
private List<SymbolRowObject> createModelData() {
List<SymbolRowObject> data = new ArrayList<>();
data.add(new TestSymbolRowObject(new TestSymbol(1, new TestAddress(101))));
data.add(new TestSymbolRowObject(new TestSymbol(2, new TestAddress(102))));
data.add(new TestSymbolRowObject(new TestSymbol(3, new TestAddress(103))));
data.add(new TestSymbolRowObject(new TestSymbol(4, new TestAddress(104))));
return data;
}
@Test
public void testRemove_DifferentInstance_SameId() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = new TestSymbolRowObject(new TestSymbol(1, new TestAddress(101)));
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(0, spyTableData.getInsertCount());
}
@Test
public void testInsert_NewSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject newSymbol =
new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
assertEquals(0, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testInsertAndRemove_NewSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject newSymbol =
new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
addRemoves.add(new AddRemoveListItem<>(REMOVE, newSymbol));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// no work was done, since the insert was followed by a remove
assertEquals(0, spyTableData.getRemoveCount());
assertEquals(0, spyTableData.getInsertCount());
}
@Test
public void testChange_NewSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject newSymbol =
new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
addRemoves.add(new AddRemoveListItem<>(CHANGE, newSymbol));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// no remove, since the symbol was not in the table
assertEquals(0, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testChange_ExisingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// no remove, since the symbol was not in the table
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testRemoveAndInsert_NewSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject newSymbol =
new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
addRemoves.add(new AddRemoveListItem<>(REMOVE, newSymbol));
addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the remove does not happen, since the time was not in the table
assertEquals(0, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testRemoveAndInsert_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
addRemoves.add(new AddRemoveListItem<>(ADD, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the remove does not happen, since the time was not in the table
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testChangeAndInsert_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
addRemoves.add(new AddRemoveListItem<>(ADD, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the insert portions get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testChangeAndRemove_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the remove portions get coalesced; no insert takes place
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(0, spyTableData.getInsertCount());
}
@Test
public void testChangeAndChange_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the changes get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testRemoveAndRemove_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the removes get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(0, spyTableData.getInsertCount());
}
@Test
public void testInsertAndInsert_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(ADD, s));
addRemoves.add(new AddRemoveListItem<>(ADD, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the inserts get coalesced
assertEquals(0, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testInsertAndChange_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(ADD, s));
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the insert portions get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testRemoveAndChange_ExistingSymbol() throws Exception {
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
SymbolRowObject s = modelData.get(0);
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the remove portions get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
@Test
public void testLostItems_Remove() throws Exception {
//
// Test that symbols get removed when the data up on which they are sorted changes before
// the removal takes place
//
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
TestSymbol symbol = (TestSymbol) modelData.get(0).getSymbol();
symbol.setName("UpdatedName");
addRemoves.add(new AddRemoveListItem<>(REMOVE, new TestSymbolRowObject(symbol)));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the insert portions get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(0, spyTableData.getInsertCount());
}
@Test
public void testLostItems_Change() throws Exception {
//
// Test that symbols get removed when the data up on which they are sorted changes before
// the removal takes place
//
List<AddRemoveListItem<SymbolRowObject>> addRemoves = new ArrayList<>();
TestSymbol symbol = (TestSymbol) modelData.get(0).getSymbol();
symbol.setName("UpdatedName");
addRemoves.add(new AddRemoveListItem<>(CHANGE, new TestSymbolRowObject(symbol)));
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
// the insert portions get coalesced
assertEquals(1, spyTableData.getRemoveCount());
assertEquals(1, spyTableData.getInsertCount());
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class SpyTableData extends TestTableData<SymbolRowObject> {
private int removeCount;
private int insertCount;
SpyTableData(List<SymbolRowObject> data, TableSortingContext<SymbolRowObject> sortContext) {
super(data, sortContext);
}
@Override
public boolean remove(SymbolRowObject t) {
removeCount++;
return super.remove(t);
}
@Override
public void insert(SymbolRowObject value) {
insertCount++;
super.insert(value);
}
int getRemoveCount() {
return removeCount;
}
int getInsertCount() {
return insertCount;
}
}
private class TestSymbolRowObject extends SymbolRowObject {
private TestSymbol sym;
public TestSymbolRowObject(TestSymbol s) {
super(s);
this.sym = s;
}
@Override
public Symbol getSymbol() {
return sym;
}
}
private class TestSymbol implements Symbol {
private long id;
private Address address;
private String name;
TestSymbol(long id, Address address) {
this.id = id;
this.address = address;
name = id + "@" + address;
}
@Override
public long getID() {
return id;
}
@Override
public Address getAddress() {
return address;
}
void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isDeleted() {
return false;
}
@Override
public String toString() {
return name;
}
@Override
public Program getProgram() {
return DUMMY_PROGRAM;
}
@Override
public SymbolType getSymbolType() {
throw new UnsupportedOperationException();
}
@Override
public ProgramLocation getProgramLocation() {
throw new UnsupportedOperationException();
}
@Override
public boolean isExternal() {
throw new UnsupportedOperationException();
}
@Override
public Object getObject() {
throw new UnsupportedOperationException();
}
@Override
public boolean isPrimary() {
throw new UnsupportedOperationException();
}
@Override
public boolean isValidParent(Namespace parent) {
throw new UnsupportedOperationException();
}
@Override
public String[] getPath() {
throw new UnsupportedOperationException();
}
@Override
public String getName(boolean includeNamespace) {
throw new UnsupportedOperationException();
}
@Override
public Namespace getParentNamespace() {
throw new UnsupportedOperationException();
}
@Override
public Symbol getParentSymbol() {
throw new UnsupportedOperationException();
}
@Override
public boolean isDescendant(Namespace namespace) {
throw new UnsupportedOperationException();
}
@Override
public int getReferenceCount() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasMultipleReferences() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasReferences() {
throw new UnsupportedOperationException();
}
@Override
public Reference[] getReferences(TaskMonitor monitor) {
throw new UnsupportedOperationException();
}
@Override
public Reference[] getReferences() {
throw new UnsupportedOperationException();
}
@Override
public void setName(String newName, SourceType source) {
throw new UnsupportedOperationException();
}
@Override
public void setNamespace(Namespace newNamespace) {
throw new UnsupportedOperationException();
}
@Override
public void setNameAndNamespace(String newName, Namespace newNamespace, SourceType source) {
throw new UnsupportedOperationException();
}
@Override
public boolean delete() {
throw new UnsupportedOperationException();
}
@Override
public boolean isPinned() {
throw new UnsupportedOperationException();
}
@Override
public void setPinned(boolean pinned) {
throw new UnsupportedOperationException();
}
@Override
public boolean isDynamic() {
throw new UnsupportedOperationException();
}
@Override
public boolean setPrimary() {
throw new UnsupportedOperationException();
}
@Override
public boolean isExternalEntryPoint() {
throw new UnsupportedOperationException();
}
@Override
public boolean isGlobal() {
throw new UnsupportedOperationException();
}
@Override
public void setSource(SourceType source) {
throw new UnsupportedOperationException();
}
@Override
public SourceType getSource() {
throw new UnsupportedOperationException();
}
}
}