GT-3226 - Symbol Table - Performance improvements - review fixes:

convert SymbolRowObject to Symbol
This commit is contained in:
dragonmacher 2019-10-24 14:19:19 -04:00
parent fc67c6aaeb
commit 9e320c6401
33 changed files with 638 additions and 624 deletions

View file

@ -120,9 +120,14 @@ public class RenameLabelCmd implements Command {
}
else {
s.setName(newName, source);
if ((newName.length() != 0 && !newName.equals(s.getName())) ||
(newName.length() == 0 && s.getSource() != SourceType.DEFAULT)) {
errorMsg = "Rename failed - default names may not be used";
if (newName.length() == 0 && s.getSource() != SourceType.DEFAULT) {
errorMsg = "Rename failed - cannot set non-default symbol name to \"\"";
return false;
}
if (!newName.equals(s.getName())) {
errorMsg = "Rename failed";
return false;
}
}

View file

@ -85,9 +85,9 @@ class ReferenceProvider extends ComponentProviderAdapter {
}
}
void symbolRemoved(long symbolID) {
void symbolRemoved(Symbol symbol) {
if (isVisible()) {
referenceKeyModel.symbolRemoved(symbolID);
referenceKeyModel.symbolRemoved(symbol);
}
}
@ -157,5 +157,4 @@ class ReferenceProvider extends ComponentProviderAdapter {
public void updateTitle() {
setSubTitle(generateSubTitle());
}
}

View file

@ -39,9 +39,9 @@ class SymbolEditor extends DefaultCellEditor {
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
if (value instanceof SymbolTableNameValue) {
SymbolTableNameValue cellValue = (SymbolTableNameValue) value;
Symbol symbol = cellValue.getSymbol();
Symbol symbol = (Symbol) value;
if (symbol != null) {
symbolField.setText(symbol.getName());
}
else {

View file

@ -46,8 +46,8 @@ class SymbolPanel extends JPanel {
private GhidraTable symTable;
private TableModelListener listener;
private FilterDialog filterDialog;
private GhidraThreadedTablePanel<SymbolRowObject> threadedTablePanel;
private GhidraTableFilterPanel<SymbolRowObject> tableFilterPanel;
private GhidraThreadedTablePanel<Symbol> threadedTablePanel;
private GhidraTableFilterPanel<Symbol> tableFilterPanel;
SymbolPanel(SymbolProvider provider, SymbolTableModel model, SymbolRenderer renderer,
final PluginTool tool, GoToService gotoService) {
@ -116,9 +116,9 @@ class SymbolPanel extends JPanel {
return tableFilterPanel;
}
protected RowFilterTransformer<SymbolRowObject> updateRowDataTransformer(boolean nameOnly) {
protected RowFilterTransformer<Symbol> updateRowDataTransformer(boolean nameOnly) {
if (nameOnly) {
return new NameOnlyRowTransformer(tableModel);
return new NameOnlyRowTransformer();
}
return new DefaultRowFilterTransformer<>(tableModel, symTable.getColumnModel());
@ -185,7 +185,7 @@ class SymbolPanel extends JPanel {
return symTable.getRowCount();
}
List<SymbolRowObject> getSelectedSymbolKeys() {
List<Symbol> getSelectedSymbolKeys() {
int[] rows = symTable.getSelectedRows();
return tableModel.getRowObjects(rows);
}
@ -198,21 +198,16 @@ class SymbolPanel extends JPanel {
// Inner Classes
//==================================================================================================
private static class NameOnlyRowTransformer implements RowFilterTransformer<SymbolRowObject> {
private static class NameOnlyRowTransformer implements RowFilterTransformer<Symbol> {
private List<String> list = new ArrayList<>();
private SymbolTableModel model;
NameOnlyRowTransformer(SymbolTableModel model) {
this.model = model;
}
@Override
public List<String> transform(SymbolRowObject rowObject) {
public List<String> transform(Symbol rowObject) {
list.clear();
Object value = model.getColumnValueForRow(rowObject, SymbolTableModel.LABEL_COL);
if (value != null) {
// the toString() returns the value for the symbol, which may be cached
list.add(value.toString());
if (rowObject != null) {
// The toString() returns the name for the symbol, which may be cached. Calling
// toString() will also avoid locking for cached values.
list.add(rowObject.toString());
}
return list;
}

View file

@ -74,17 +74,18 @@ class SymbolProvider extends ComponentProviderAdapter {
if (program == null) {
return null;
}
List<SymbolRowObject> rowObjects = symbolPanel.getSelectedSymbolKeys();
List<Symbol> rowObjects = symbolPanel.getSelectedSymbolKeys();
long[] symbolIDs = new long[rowObjects.size()];
int index = 0;
for (SymbolRowObject obj : rowObjects) {
symbolIDs[index++] = obj.getKey();
for (Symbol obj : rowObjects) {
symbolIDs[index++] = obj.getID();
}
return new ProgramSymbolActionContext(this, program, symbolIDs, getTable());
}
void deleteSymbols() {
List<SymbolRowObject> rowObjects = symbolPanel.getSelectedSymbolKeys();
List<Symbol> rowObjects = symbolPanel.getSelectedSymbolKeys();
symbolKeyModel.delete(rowObjects);
}
@ -93,15 +94,15 @@ class SymbolProvider extends ComponentProviderAdapter {
}
Symbol getCurrentSymbol() {
List<SymbolRowObject> rowObjects = symbolPanel.getSelectedSymbolKeys();
List<Symbol> rowObjects = symbolPanel.getSelectedSymbolKeys();
if (rowObjects != null && rowObjects.size() >= 1) {
return symbolKeyModel.getSymbol(rowObjects.get(0).getKey());
return rowObjects.get(0);
}
return null;
}
Symbol getSymbolForRow(int row) {
return symbolKeyModel.getRowObject(row).getSymbol();
return symbolKeyModel.getRowObject(row);
}
void setCurrentSymbol(Symbol symbol) {
@ -136,12 +137,6 @@ class SymbolProvider extends ComponentProviderAdapter {
}
}
void symbolRemoved(long symbolId) {
if (isVisible()) {
symbolKeyModel.symbolRemoved(symbolId);
}
}
void symbolChanged(Symbol s) {
if (isVisible()) {
symbolKeyModel.symbolChanged(s);

View file

@ -131,8 +131,8 @@ public class SymbolReferenceModel extends AddressBasedTableModel<Reference> {
checkRefs(symbol);
}
void symbolRemoved(long symbolID) {
if (currentSymbol != null && currentSymbol.getID() == symbolID) {
void symbolRemoved(Symbol symbol) {
if (currentSymbol != null && currentSymbol.getID() == symbol.getID()) {
setCurrentSymbol(null);
}
}

View file

@ -52,10 +52,6 @@ class SymbolRenderer extends GhidraTableCellRenderer {
if (value == null && column == SymbolTableModel.LABEL_COL) {
setText("<< REMOVED >>");
}
else if (value instanceof SymbolTableNameValue) {
Symbol symbol = ((SymbolTableNameValue) value).getSymbol();
handleSymbol(symbol, isSelected);
}
else if (value instanceof Symbol) {
handleSymbol(value, isSelected);
}

View file

@ -1,76 +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 ghidra.program.model.symbol.Symbol;
class SymbolRowObject implements Comparable<SymbolRowObject> {
// symbol can be null after it is deleted
private final Symbol symbol;
private final long key;
SymbolRowObject(Symbol s) {
this.symbol = s;
this.key = s.getID();
}
// this constructor is used to create a row object to serve as a key for deleting items
// in the model after a symbol has been deleted
SymbolRowObject(long symbolId) {
this.symbol = null;
this.key = symbolId;
}
Symbol getSymbol() {
return symbol;
}
long getKey() {
return key;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (key ^ (key >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SymbolRowObject other = (SymbolRowObject) obj;
if (key != other.key) {
return false;
}
return true;
}
@Override
public int compareTo(SymbolRowObject o) {
return ((Long) key).compareTo(o.key);
}
}

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.
@ -20,21 +19,17 @@ import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.table.ProgramLocationTableRowMapper;
public class SymbolRowObjectToAddressTableRowMapper extends
ProgramLocationTableRowMapper<SymbolRowObject, Address> {
public class SymbolRowObjectToAddressTableRowMapper
extends ProgramLocationTableRowMapper<Symbol, Address> {
@Override
public Address map(SymbolRowObject rowObject, Program data, ServiceProvider serviceProvider) {
SymbolTable symbolTable = data.getSymbolTable();
Symbol symbol = symbolTable.getSymbol(rowObject.getKey());
if (symbol == null) {
public Address map(Symbol rowObject, Program data, ServiceProvider serviceProvider) {
if (rowObject == null) {
return null;
}
return symbol.getAddress();
return rowObject.getAddress();
}
}

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.
@ -19,23 +18,15 @@ package ghidra.app.plugin.core.symtable;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.util.ProgramLocation;
import ghidra.util.table.ProgramLocationTableRowMapper;
public class SymbolRowObjectToProgramLocationTableRowMapper extends
ProgramLocationTableRowMapper<SymbolRowObject, ProgramLocation> {
public class SymbolRowObjectToProgramLocationTableRowMapper
extends ProgramLocationTableRowMapper<Symbol, ProgramLocation> {
@Override
public ProgramLocation map(SymbolRowObject rowObject, Program data,
ServiceProvider serviceProvider) {
SymbolTable symbolTable = data.getSymbolTable();
Symbol symbol = symbolTable.getSymbol(rowObject.getKey());
if (symbol == null) {
return null;
}
return symbol.getProgramLocation();
public ProgramLocation map(Symbol rowObject, Program data, ServiceProvider serviceProvider) {
return rowObject.getProgramLocation();
}
}

View file

@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.symtable;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
import docking.widgets.table.*;
import ghidra.app.cmd.function.DeleteFunctionCmd;
@ -41,7 +40,12 @@ import ghidra.util.table.column.*;
import ghidra.util.table.field.*;
import ghidra.util.task.TaskMonitor;
class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
class SymbolTableModel extends AddressBasedTableModel<Symbol> {
private static final Comparator<Symbol> NAME_COL_COMPARATOR = (s1, s2) -> {
return s1.toString().compareToIgnoreCase(s2.toString());
};
static final int LABEL_COL = 0;
static final int LOCATION_COL = 1;
static final int TYPE_COL = 2;
@ -62,17 +66,14 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
this.provider = provider;
this.tool = tool;
this.filter = new NewSymbolFilter();
// leave off default sorting, as this can be slow; the user can sort as desired
setDefaultTableSortState(null);
}
@Override
protected TableColumnDescriptor<SymbolRowObject> createTableColumnDescriptor() {
TableColumnDescriptor<SymbolRowObject> descriptor = new TableColumnDescriptor<>();
protected TableColumnDescriptor<Symbol> createTableColumnDescriptor() {
TableColumnDescriptor<Symbol> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new NameTableColumn(), 1, true);
descriptor.addVisibleColumn(new LocationTableColumn());
descriptor.addVisibleColumn(new NameTableColumn());
descriptor.addVisibleColumn(new LocationTableColumn(), 1, true);
descriptor.addVisibleColumn(new SymbolTypeTableColumn());
descriptor.addHiddenColumn(new DataTypeTableColumn());
descriptor.addVisibleColumn(new NamespaceTableColumn());
@ -136,7 +137,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
protected void doLoad(Accumulator<SymbolRowObject> accumulator, TaskMonitor monitor)
protected void doLoad(Accumulator<Symbol> accumulator, TaskMonitor monitor)
throws CancelledException {
if (symbolTable == null) {
return;
@ -156,7 +157,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
monitor.checkCanceled();
Symbol s = it.next();
if (filter.accepts(s, getProgram())) {
accumulator.add(new SymbolRowObject(s));
accumulator.add(s);
}
}
if (filter.acceptsDefaultLabelSymbols()) {
@ -168,7 +169,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
Address a = addrIt.next();
Symbol s = symbolTable.getPrimarySymbol(a);
if (s.isDynamic() && filter.accepts(s, getProgram())) {
accumulator.add(new SymbolRowObject(s));
accumulator.add(s);
}
}
}
@ -193,8 +194,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
return;
}
SymbolRowObject rowObject = filteredData.get(row);
Symbol symbol = symbolTable.getSymbol(rowObject.getKey());
Symbol symbol = filteredData.get(row);
if (symbol == null) {
return;
}
@ -215,9 +215,9 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public ProgramLocation getProgramLocation(int row, int column) {
SymbolTableNameValue s = (SymbolTableNameValue) getValueAt(row, LABEL_COL);
Symbol s = (Symbol) getValueAt(row, LABEL_COL);
if (s != null) {
return s.getSymbol().getProgramLocation();
return s.getProgramLocation();
}
return null;
}
@ -246,7 +246,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
void symbolAdded(Symbol s) {
if (filter.accepts(s, getProgram())) {
addObject(new SymbolRowObject(s));
addObject(s);
lastSymbol = s;
}
}
@ -255,27 +255,20 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
if (lastSymbol != null && lastSymbol.getID() == s.getID()) {
lastSymbol = null;
}
removeObject(new SymbolRowObject(s));
}
void symbolRemoved(long symbolId) {
if (lastSymbol != null && lastSymbol.getID() == symbolId) {
lastSymbol = null;
}
removeObject(new SymbolRowObject(symbolId));
removeObject(s);
}
void symbolChanged(Symbol s) {
SymbolRowObject symbolRowObject = new SymbolRowObject(s);
Symbol Symbol = s;
if (filter.accepts(s, getProgram())) {
updateObject(symbolRowObject);
updateObject(Symbol);
}
else {
removeObject(symbolRowObject);
removeObject(Symbol);
}
}
void delete(List<SymbolRowObject> rowObjects) {
void delete(List<Symbol> rowObjects) {
if (rowObjects == null || rowObjects.size() == 0) {
return;
}
@ -283,11 +276,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
tool.setStatusInfo("");
List<Symbol> deleteList = new LinkedList<>();
CompoundCmd cmd = new CompoundCmd("Delete symbol(s)");
for (int i = 0; i < rowObjects.size(); i++) {
Symbol symbol = symbolTable.getSymbol(rowObjects.get(i).getKey());
if (symbol == null) {
continue;
}
for (Symbol symbol : rowObjects) {
if (symbol.isDynamic()) {
Symbol[] symbols = symbolTable.getSymbols(symbol.getAddress());
if (symbols.length == 1) {
@ -296,7 +285,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
}
deleteList.add(rowObjects.get(i).getSymbol());
deleteList.add(symbol);
String label = symbol.getName();
if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject();
@ -316,9 +305,10 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
if (cmd.size() == 0) {
return;
}
if (tool.execute(cmd, getProgram())) {
for (int k = 0; k < deleteList.size(); k++) {
removeObject(new SymbolRowObject(deleteList.get(k)));
for (Symbol s : deleteList) {
removeObject(s);
}
updateNow();
}
@ -334,15 +324,14 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public Address getAddress(int row) {
Symbol symbol = symbolTable.getSymbol(getRowObject(row).getKey());
Symbol symbol = getRowObject(row);
if (symbol == null) {
return null;
}
return symbol.getAddress();
}
private AddressBasedLocation getSymbolLocation(SymbolRowObject rowObject) {
Symbol s = rowObject.getSymbol();
private AddressBasedLocation getSymbolLocation(Symbol s) {
if (s == null) {
return new AddressBasedLocation();
}
@ -350,17 +339,36 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
if (type == SymbolType.PARAMETER || type == SymbolType.LOCAL_VAR) {
// Must use special location object for variables which renders variable storage
// location since this can't be obtained from just a variable storage address
return new VariableSymbolLocation((Variable) s.getObject());
Variable object = (Variable) s.getObject();
if (object == null) {
return null;
}
return new VariableSymbolLocation(object);
}
return new AddressBasedLocation(program, s.getAddress());
}
@Override
protected Comparator<Symbol> createSortComparator(int columnIndex) {
DynamicTableColumn<Symbol, ?, ?> 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
// 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
// ThreadedTableModel.getCachedColumnValueForRow for more info.
return NAME_COL_COMPARATOR;
}
return super.createSortComparator(columnIndex);
}
//==================================================================================================
// Table Column Classes
//==================================================================================================
private class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SymbolTableNameValue> {
private class NameTableColumn extends AbstractProgramBasedDynamicTableColumn<Symbol, Symbol> {
@Override
public String getColumnName() {
@ -368,25 +376,18 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public SymbolTableNameValue getValue(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
public Symbol getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol s = rowObject.getSymbol();
if (s == null) {
if (!symbol.checkIsValid()) {
return null;
}
// Note: this call is slow, especially for dynamic symbols. Caching the dynamic
// symbols in the SymbolRowObject *greatly* increases sorting and filtering performance.
// For now we assume that most users are not loading dynamic labels. If we add
// caching, then we have to deal with the stickiness of when to clear/update the cache
String name = s.toString();
return new SymbolTableNameValue(s, name);
return symbol;
}
}
private class PinnedTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Boolean> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, Boolean> {
private PinnedRenderer renderer = new PinnedRenderer();
@ -396,10 +397,10 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public Boolean getValue(SymbolRowObject rowObject, Settings settings, Program p,
public Boolean getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
if (!symbol.checkIsValid()) {
return null;
}
return symbol.isPinned();
@ -417,7 +418,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class LocationTableColumn
extends AbstractProgramLocationTableColumn<SymbolRowObject, AddressBasedLocation> {
extends AbstractProgramLocationTableColumn<Symbol, AddressBasedLocation> {
@Override
public String getColumnName() {
@ -425,16 +426,16 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public AddressBasedLocation getValue(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
return getSymbolLocation(rowObject);
public AddressBasedLocation getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
return getSymbolLocation(symbol);
}
@Override
public ProgramLocation getProgramLocation(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
public ProgramLocation getProgramLocation(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) {
if (!symbol.checkIsValid()) {
return null;
}
return symbol.getProgramLocation();
@ -442,7 +443,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class SymbolTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, String> {
@Override
public String getColumnName() {
@ -450,17 +451,16 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
public String getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol s = rowObject.getSymbol();
if (s == null) {
if (!symbol.checkIsValid()) {
return null;
}
// Note: this call is slow. If we decide that filtering/sorting on this value is
// important, then this should be cached
return SymbolUtilities.getSymbolTypeDisplayName(s);
return SymbolUtilities.getSymbolTypeDisplayName(symbol);
}
}
@ -472,7 +472,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class DataTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, String> {
@Override
public String getColumnName() {
@ -480,11 +480,10 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
public String getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
if (!symbol.checkIsValid()) {
return null;
}
@ -510,7 +509,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class NamespaceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, String> {
@Override
public String getColumnName() {
@ -518,19 +517,18 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
public String getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
if (!symbol.checkIsValid()) {
return null;
}
return symbol.getParentNamespace().getName(true);
}
}
private class SourceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SourceType> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, SourceType> {
private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<SourceType>() {
@Override
@ -558,9 +556,9 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public SourceType getValue(SymbolRowObject rowObject, Settings settings, Program p,
public SourceType getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -570,7 +568,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class ReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, Integer> {
private ReferenceCountRenderer renderer = new ReferenceCountRenderer();
@ -580,13 +578,11 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
public Integer getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
if (!symbol.checkIsValid()) {
return null;
}
return Integer.valueOf(symbol.getReferenceCount());
}
@ -604,7 +600,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class OffcutReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, Integer> {
private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer();
@ -614,11 +610,9 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
public Integer getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
if (!symbol.checkIsValid()) {
return null;
}
@ -655,8 +649,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
}
private class UserTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
private class UserTableColumn extends AbstractProgramBasedDynamicTableColumn<Symbol, String> {
@Override
public String getColumnName() {
@ -669,11 +662,10 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
public String getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
if (!symbol.checkIsValid()) {
return null;
}
@ -694,7 +686,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
private class OriginalNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
extends AbstractProgramBasedDynamicTableColumn<Symbol, String> {
@Override
public String getColumnName() {
@ -707,13 +699,17 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
public String getValue(Symbol symbol, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || !symbol.isExternal()) {
if (!symbol.checkIsValid()) {
return null;
}
if (!symbol.isExternal()) {
return null;
}
SymbolType symbolType = symbol.getSymbolType();
if (symbolType != SymbolType.FUNCTION && symbolType != SymbolType.LABEL) {
return null;

View file

@ -1,52 +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 ghidra.program.model.symbol.Symbol;
/**
* A simple data object for the Name column table cell. This class allows us to control
* how sorting is performed by caching the slow (potentially) to calculate symbol name.
*/
class SymbolTableNameValue implements Comparable<SymbolTableNameValue> {
private Symbol symbol;
private String name;
SymbolTableNameValue(Symbol symbol, String name) {
this.symbol = symbol;
this.name = name;
// name will be non-null when cached by the table model
if (name == null) {
name = symbol.toString();
}
}
Symbol getSymbol() {
return symbol;
}
@Override
public int compareTo(SymbolTableNameValue o) {
return name.compareToIgnoreCase(o.name);
}
@Override
public String toString() {
return name;
}
}

View file

@ -37,8 +37,7 @@ import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ChangeManager;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.table.GhidraTable;
@ -210,11 +209,12 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
ProgramChangeRecord rec = (ProgramChangeRecord) doRecord;
Symbol symbol = null;
SymbolTable symbolTable = currentProgram.getSymbolTable();
switch (eventType) {
case ChangeManager.DOCR_CODE_ADDED:
case ChangeManager.DOCR_CODE_REMOVED:
if (rec.getNewValue() instanceof Data) {
symbol = currentProgram.getSymbolTable().getPrimarySymbol(rec.getStart());
symbol = symbolTable.getPrimarySymbol(rec.getStart());
if (symbol != null && symbol.isDynamic()) {
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
@ -224,7 +224,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
case ChangeManager.DOCR_SYMBOL_ADDED:
Address addAddr = rec.getStart();
Symbol primaryAtAdd = currentProgram.getSymbolTable().getPrimarySymbol(addAddr);
Symbol primaryAtAdd = symbolTable.getPrimarySymbol(addAddr);
if (primaryAtAdd != null && primaryAtAdd.isDynamic()) {
symProvider.symbolRemoved(primaryAtAdd);
}
@ -236,10 +236,11 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
case ChangeManager.DOCR_SYMBOL_REMOVED:
Address removeAddr = rec.getStart();
Long symbolID = (Long) rec.getNewValue();
symProvider.symbolRemoved(symbolID.longValue());
refProvider.symbolRemoved(symbolID.longValue());
Symbol primaryAtRemove =
currentProgram.getSymbolTable().getPrimarySymbol(removeAddr);
Symbol removedSymbol =
symbolTable.createSymbolPlaceholder(removeAddr, symbolID);
symProvider.symbolRemoved(removedSymbol);
refProvider.symbolRemoved(removedSymbol);
Symbol primaryAtRemove = symbolTable.getPrimarySymbol(removeAddr);
if (primaryAtRemove != null && primaryAtRemove.isDynamic()) {
symProvider.symbolAdded(primaryAtRemove);
}
@ -274,7 +275,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
break;
case ChangeManager.DOCR_MEM_REFERENCE_ADDED:
Reference ref = (Reference) rec.getObject();
symbol = currentProgram.getSymbolTable().getSymbol(ref);
symbol = symbolTable.getSymbol(ref);
if (symbol != null) {
symProvider.symbolChanged(symbol);
refProvider.symbolChanged(symbol);
@ -284,11 +285,12 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
ref = (Reference) rec.getObject();
Address toAddr = ref.getToAddress();
if (toAddr.isMemoryAddress()) {
symbol = currentProgram.getSymbolTable().getSymbol(ref);
symbol = symbolTable.getSymbol(ref);
if (symbol == null) {
long id = currentProgram.getSymbolTable().getDynamicSymbolID(
ref.getToAddress());
symProvider.symbolRemoved(id);
long id = symbolTable.getDynamicSymbolID(ref.getToAddress());
removedSymbol = symbolTable.createSymbolPlaceholder(toAddr, id);
symProvider.symbolRemoved(removedSymbol);
}
else {
refProvider.symbolChanged(symbol);
@ -298,7 +300,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_ADDED:
case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_REMOVED:
Symbol[] symbols = currentProgram.getSymbolTable().getSymbols(rec.getStart());
Symbol[] symbols = symbolTable.getSymbols(rec.getStart());
for (Symbol element : symbols) {
symProvider.symbolChanged(element);
refProvider.symbolChanged(element);

View file

@ -47,7 +47,8 @@ import ghidra.util.exception.AssertException;
import ghidra.util.exception.RollbackException;
import junit.framework.AssertionFailedError;
import utility.application.ApplicationLayout;
import utility.function.*;
import utility.function.ExceptionalCallback;
import utility.function.ExceptionalFunction;
public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDockingTest {
@ -107,7 +108,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
* if found. If no language is found, an exception will be thrown.
* @param oldLanguageName old language name string
* @return the language compiler and spec
* @throws LanguageNotFoundException
* @throws LanguageNotFoundException if the language is not found
*/
public static LanguageCompilerSpecPair getLanguageCompilerSpecPair(String oldLanguageName)
throws LanguageNotFoundException {
@ -194,7 +195,14 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
}
}
public static <E extends Exception> void tx(Program p, ExceptionalCallback<E> c) throws E {
/**
* Provides a convenient method for modifying the current program, handling the transaction
* logic.
*
* @param p the program
* @param c the code to execute
*/
public static <E extends Exception> void tx(Program p, ExceptionalCallback<E> c) {
int txId = p.startTransaction("Test - Function in Transaction");
boolean commit = true;
try {
@ -202,9 +210,9 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
p.flushEvents();
waitForSwing();
}
catch (RollbackException e) {
catch (Exception e) {
commit = false;
throw e;
failWithException("Exception modifying program '" + p.getName() + "'", e);
}
finally {
p.endTransaction(txId, commit);
@ -213,27 +221,14 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
/**
* Provides a convenient method for modifying the current program, handling the transaction
* logic
* logic. This method is calls {@link #tx(Program, ExceptionalCallback)}, but helps with
* semantics.
*
* @param program the program
* @param callback the code to execute
* @param p the program
* @param c the code to execute
*/
public <E extends Exception> void modifyProgram(Program program,
ExceptionalConsumer<Program, E> callback) {
assertNotNull("Program cannot be null", program);
boolean commit = false;
int tx = program.startTransaction("Test");
try {
callback.accept(program);
commit = true;
}
catch (Exception e) {
failWithException("Exception modifying program '" + program.getName() + "'", e);
}
finally {
program.endTransaction(tx, commit);
}
public static <E extends Exception> void modifyProgram(Program p, ExceptionalCallback<E> c) {
tx(p, c);
}
/**
@ -244,7 +239,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
* @param f the function for modifying the program and creating the desired result
* @return the result
*/
public <R, E extends Exception> R createInProgram(Program program,
public <R, E extends Exception> R modifyProgram(Program program,
ExceptionalFunction<Program, R, E> f) {
assertNotNull("Program cannot be null", program);

View file

@ -27,7 +27,6 @@ import java.util.function.BiConsumer;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel;
import org.jdom.Element;
@ -41,6 +40,7 @@ import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.label.CreateNamespacesCmd;
import ghidra.app.cmd.refs.RemoveReferenceCmd;
import ghidra.app.plugin.core.clear.ClearCmd;
import ghidra.app.plugin.core.clear.ClearOptions;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
@ -68,8 +68,15 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private CodeBrowserPlugin browser;
private CodeBrowserPlugin cbPlugin;
private SymbolTablePlugin plugin;
private ProgramDB program;
private GTable symbolTable;
private SymbolTableModel symbolModel;
private GTable referenceTable;
private GhidraTableFilterPanel<Symbol> filterPanel;
private SymbolProvider provider;
private DockingActionIf viewSymAction;
private DockingActionIf viewRefAction;
private DockingActionIf deleteAction;
@ -77,13 +84,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private DockingActionIf setPinnedAction;
private DockingActionIf clearPinnedAction;
private DockingActionIf setFilterAction;
private ProgramDB prog;
private GTable symbolTable;
private SymbolTableModel symbolModel;
private JTableHeader symbolTableHeader;
private GTable referenceTable;
private GhidraTableFilterPanel<SymbolRowObject> filterPanel;
private SymbolProvider provider;
@Before
public void setUp() throws Exception {
@ -93,7 +93,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
tool = env.getTool();
configureTool(tool);
browser = env.getPlugin(CodeBrowserPlugin.class);
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
plugin = env.getPlugin(SymbolTablePlugin.class);
provider = (SymbolProvider) getInstanceField("symProvider", plugin);
@ -120,12 +120,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testNavigation() throws Exception {
openProgram("sample");
int row = findRow("ghidra", "Global");
int row = findRow("ghidra");
TableModel model = symbolTable.getModel();
doubleClick(symbolTable, row, SymbolTableModel.LOCATION_COL);
ProgramLocation pl = getProgramLocation(row, SymbolTableModel.LOCATION_COL, model);
assertEquals(pl.getAddress(), browser.getCurrentAddress());
assertEquals(pl.getAddress(), cbPlugin.getCurrentAddress());
}
@Test
@ -351,7 +351,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForNotBusy(symbolTable);
String symbolName = "ghidra";
int row = findRow(symbolName, "Global");
int row = findRow(symbolName);
doubleClick(symbolTable, row, SymbolTableModel.LABEL_COL);
waitForSwing();
@ -379,49 +379,40 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
public void testQuickLookup() throws Exception {
openProgram("sample");
int id = prog.startTransaction(testName.getMethodName());
try {
Address sample = prog.getMinAddress();
SymbolTable st = prog.getSymbolTable();
tx(program, () -> {
Address sample = program.getMinAddress();
SymbolTable st = program.getSymbolTable();
st.createLabel(sample.getNewAddress(0x01008100), "_", SourceType.USER_DEFINED);
st.createLabel(sample.getNewAddress(0x01008100), "a", SourceType.USER_DEFINED);
st.createLabel(sample.getNewAddress(0x01008200), "ab", SourceType.USER_DEFINED);
st.createLabel(sample.getNewAddress(0x01008300), "abc", SourceType.USER_DEFINED);
st.createLabel(sample.getNewAddress(0x01008400), "abc1", SourceType.USER_DEFINED);
st.createLabel(sample.getNewAddress(0x01008500), "abc123", SourceType.USER_DEFINED);
}
finally {
prog.endTransaction(id, true);
}
});
waitForNotBusy(symbolTable);
selectRow(0);
triggerAutoLookup("a");
waitForNotBusy(symbolTable);
assertEquals(findRow("a", "Global"), symbolTable.getSelectedRow());
Thread.sleep(GTable.KEY_TIMEOUT);
sleep(GTable.KEY_TIMEOUT);
triggerAutoLookup("ab");
waitForNotBusy(symbolTable);
assertEquals(findRow("ab", "Global"), symbolTable.getSelectedRow());
Thread.sleep(GTable.KEY_TIMEOUT);
sleep(GTable.KEY_TIMEOUT);
triggerAutoLookup("abc");
waitForNotBusy(symbolTable);
assertEquals(findRow("abc", "Global"), symbolTable.getSelectedRow());
Thread.sleep(GTable.KEY_TIMEOUT);
sleep(GTable.KEY_TIMEOUT);
triggerAutoLookup("abcd");
waitForNotBusy(symbolTable);
assertEquals(findRow("abc1", "Global"), symbolTable.getSelectedRow());
Thread.sleep(GTable.KEY_TIMEOUT);
sleep(GTable.KEY_TIMEOUT);
selectRow(0);
waitForSwing();
triggerAutoLookup("abc12");
waitForNotBusy(symbolTable);
assertEquals(findRow("abc123", "Global"), symbolTable.getSelectedRow());
}
@ -432,40 +423,40 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
int rowCount = symbolTable.getRowCount();
assertTrue(!deleteAction.isEnabled());
final int row = findRow("ghidra", "Global");
int row = findRow("ghidra");
Rectangle rect = symbolTable.getCellRect(row, 0, true);
symbolTable.scrollRectToVisible(rect);
singleClick(symbolTable, row, 0);
assertTrue(deleteAction.isEnabled());
performAction(deleteAction, true);
waitForNotBusy(symbolTable);
assertNull(getUniqueSymbol(prog, "ghidra"));
Symbol myLocalSymbol = getUniqueSymbol(prog, "MyLocal");
assertNull(getUniqueSymbol(program, "ghidra"));
Symbol myLocalSymbol = getUniqueSymbol(program, "MyLocal");
assertNotNull(myLocalSymbol);// MyLocal should have been promoted to global since user defined.
assertEquals(SourceType.USER_DEFINED, myLocalSymbol.getSource());
assertEquals(prog.getGlobalNamespace(), myLocalSymbol.getParentNamespace());
Symbol anotherLocalSymbol = getUniqueSymbol(prog, "AnotherLocal");
assertEquals(program.getGlobalNamespace(), myLocalSymbol.getParentNamespace());
int rowAfterDelete = findRow("ghidra");
assertEquals(-1, rowAfterDelete);
Symbol anotherLocalSymbol = getUniqueSymbol(program, "AnotherLocal");
assertNotNull(anotherLocalSymbol);// AnotherLocal should have been promoted to global since user defined.
assertEquals(SourceType.USER_DEFINED, anotherLocalSymbol.getSource());
assertEquals(prog.getGlobalNamespace(), anotherLocalSymbol.getParentNamespace());
assertEquals(program.getGlobalNamespace(), anotherLocalSymbol.getParentNamespace());
// 1 Function label removed (1 dynamic added at function entry)
// Locals were promoted to global.
assertEquals(rowCount, symbolTable.getRowCount());
int newDynamicSymbolRow = findRow("SUB_00000052");
assertFalse(newDynamicSymbolRow == -1);
final int anotherLocal_RowIndex = findRow("AnotherLocal", "Global");
int anotherLocal_RowIndex = findRow("AnotherLocal");
selectRow(anotherLocal_RowIndex);
int selectedRow = symbolTable.getSelectedRow();
assertEquals("Row was not selected!", anotherLocal_RowIndex, selectedRow);
waitForSwing();
performAction(deleteAction, true);
anotherLocalSymbol = getUniqueSymbol(prog, "AnotherLocal");
anotherLocalSymbol = getUniqueSymbol(program, "AnotherLocal");
assertNull("Delete action did not delete symbol: " + anotherLocalSymbol,
anotherLocalSymbol);// AnotherLocal should have been promoted to global since user defined.
@ -484,10 +475,10 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Address addr = addr("0x100");
// grab the test symbol from the symbol table database and make sure it exists
FunctionManager functionManager = prog.getFunctionManager();
FunctionManager functionManager = program.getFunctionManager();
Function function = functionManager.getFunctionContaining(addr);
Symbol param1Symbol = getUniqueSymbol(prog, "param_1", function);
Symbol param1Symbol = getUniqueSymbol(program, "param_1", function);
assertNotNull("Could not find param_1 in function", param1Symbol);
@ -498,7 +489,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
// execute the delete action
performAction(deleteAction, true);
Assert.assertNotEquals(param1Symbol, getUniqueSymbol(prog, "param_1", function));
Assert.assertNotEquals(param1Symbol, getUniqueSymbol(program, "param_1", function));
}
@Test
@ -521,9 +512,11 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(!makeSelectionAction.isEnabled());
final int row = findRow("ghidra", "Global");
int row1 = findRow("ghidra");
int row2 = findRow("KERNEL32.dll_GetProcAddress");
int row3 = findRow("LAB_00000058");
int rowCount = 3;
selectRow(row, row + 2);
selectRows(row1, row2, row3);
assertTrue(makeSelectionAction.isEnabled());
@ -533,11 +526,11 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
performAction(makeSelectionAction, true);
waitForSwing();
ProgramSelection sel = browser.getCurrentSelection();
ProgramSelection sel = cbPlugin.getCurrentSelection();
assertEquals(rowCount, sel.getNumAddressRanges());
Address sample = prog.getMinAddress();
Address sample = program.getMinAddress();
long address = 0x52;
assertTrue("Selection does not contain address: " + address + " - selection: " + sel,
@ -555,21 +548,23 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
public void testSetAndClearPinnedAction() throws Exception {
openProgram("sample");
int row = findRow("ADVAPI32.dll_IsTextUnicode", "Global");
selectRow(row, row + 2);
int row1 = findRow("ADVAPI32.dll_IsTextUnicode");
int row2 = findRow("AnotherLocal", "ghidra");
int row3 = findRow("CharLowerW");
selectRows(row1, row2, row3);
ActionContext actionContext = provider.getActionContext(null);
int[] selectedRows = symbolTable.getSelectedRows();
assertEquals(3, selectedRows.length);
for (int selectedRow : selectedRows) {
Symbol symbol = getSymbol(selectedRow);
assertTrue(!symbol.isPinned());
assertFalse(symbol.isPinned());
}
assertTrue(setPinnedAction.isEnabledForContext(actionContext));
assertTrue(!clearPinnedAction.isEnabledForContext(actionContext));
assertFalse(clearPinnedAction.isEnabledForContext(actionContext));
performAction(setPinnedAction, actionContext, true);
waitForSwing();
waitForNotBusy(symbolTable);
for (int selectedRow : selectedRows) {
Symbol symbol = getSymbol(selectedRow);
assertTrue(symbol.isPinned());
@ -579,17 +574,17 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForSwing();
for (int selectedRow : selectedRows) {
Symbol symbol = getSymbol(selectedRow);
assertTrue(!symbol.isPinned());
assertFalse(symbol.isPinned());
}
}
@Test
public void testSetPinnedActionNotEnabledForExternalSymbols() throws Exception {
openProgram("sample");
int row = findRow("CharLowerW", "USER32.dll");
selectRow(row, row + 1);
int row1 = findRow("CharLowerW", "USER32.dll");
int row2 = findRow("CharLowerZ", "USER32.dll");
selectRows(row1, row2);
ActionContext actionContext = provider.getActionContext(null);
int[] selectedRows = symbolTable.getSelectedRows();
@ -606,26 +601,25 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testUpdateOnSymbolsAdded() throws Exception {
openProgram("sample");
Address sample = prog.getMinAddress();
SymbolTable st = prog.getSymbolTable();
Symbol sym = null;
Address sample = program.getMinAddress();
SymbolTable st = program.getSymbolTable();
int rowCount = symbolTable.getRowCount();
int id = prog.startTransaction(testName.getMethodName());
try {
sym = st.createLabel(sample.getNewAddress(0x01007000), "Zeus", SourceType.USER_DEFINED);
Symbol sym = modifyProgram(program, p -> {
return st.createLabel(sample.getNewAddress(0x01007000), "Zeus",
SourceType.USER_DEFINED);
});
waitForNotBusy(symbolTable);
assertEquals(rowCount + 1, symbolTable.getRowCount());
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym)) >= 0);
assertTrue(symbolModel.getRowIndex(sym) >= 0);
sym =
st.createLabel(sample.getNewAddress(0x01007100), "Athena", SourceType.USER_DEFINED);
sym = modifyProgram(program, p -> {
return st.createLabel(sample.getNewAddress(0x01007100), "Athena",
SourceType.USER_DEFINED);
});
waitForNotBusy(symbolTable);
assertEquals(rowCount + 2, symbolTable.getRowCount());
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym)) >= 0);
}
finally {
prog.endTransaction(id, true);
}
assertTrue(symbolModel.getRowIndex(sym) >= 0);
}
@Test
@ -646,22 +640,17 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
myTypeText(textField, "s");
int rowCount = symbolModel.getRowCount();
Address sample = prog.getMinAddress();
SymbolTable st = prog.getSymbolTable();
Symbol sym = null;
int id = prog.startTransaction(testName.getMethodName());
try {
sym =
st.createLabel(sample.getNewAddress(0x01007000), "saaaa", SourceType.USER_DEFINED);
Address sample = program.getMinAddress();
SymbolTable st = program.getSymbolTable();
Symbol sym = modifyProgram(program, p -> {
return st.createLabel(sample.getNewAddress(0x01007000), "saaaa",
SourceType.USER_DEFINED);
});
waitForNotBusy(symbolTable);
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym)) >= 0);
}
finally {
prog.endTransaction(id, true);
}
assertTrue(symbolModel.getRowIndex(sym) >= 0);
waitForNotBusy(symbolTable);
assertEquals(rowCount + 1, symbolModel.getRowCount());// make sure we added one while the filter is on
// make sure we added one while the filter is on
assertEquals(rowCount + 1, symbolModel.getRowCount());
}
@Test
@ -688,64 +677,53 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
//
assertEquals(22, symbolTable.getRowCount());
Symbol symbol = getUniqueSymbol(prog, "ghidra");
Symbol symbol = getUniqueSymbol(program, "ghidra");
setName(symbol, null, SourceType.DEFAULT);
assertEquals(21, symbolTable.getRowCount());
setName(symbol, "foobar", SourceType.USER_DEFINED);
assertEquals(22, symbolTable.getRowCount());
}
@Test
public void testUpdateOnSymbolsRemoved() throws Exception {
openProgram("sample");
SymbolTable st = prog.getSymbolTable();
Symbol sym = getUniqueSymbol(prog, "entry");
assertNull(getUniqueSymbol(prog, "EXT_00000051"));
SymbolTable st = program.getSymbolTable();
Symbol sym = getUniqueSymbol(program, "entry");
assertNull(getUniqueSymbol(program, "EXT_00000051"));
int id = prog.startTransaction(testName.getMethodName());
try {
st.removeSymbolSpecial(sym);
}
finally {
prog.endTransaction(id, true);
}
tx(program, () -> st.removeSymbolSpecial(sym));
waitForNotBusy(symbolTable);
// entry symbol replaced by dynamic External Entry symbol
assertNull(getUniqueSymbol(prog, "entry"));
assertNotNull(getUniqueSymbol(prog, "EXT_00000051"));
assertTrue("Deleted symbol not removed from table",
symbolModel.getRowIndex(new SymbolRowObject(sym)) < 0);
assertNull(getUniqueSymbol(program, "entry"));
assertNotNull(getUniqueSymbol(program, "EXT_00000051"));
assertTrue("Deleted symbol not removed from table", symbolModel.getRowIndex(sym) < 0);
}
@Test
public void testUpdateOnReferencesAdded() throws Exception {
openProgram("sample");
Address sample = prog.getMinAddress();
Address sample = program.getMinAddress();
Symbol s = getUniqueSymbol(prog, "entry");
Symbol s = getUniqueSymbol(program, "entry");
int row = symbolModel.getRowIndex(new SymbolRowObject(s));
int row = symbolModel.getRowIndex(s);
Integer refCount = getRefCount(row);
assertNotNull(refCount);
assertEquals(3, refCount.intValue());
ReferenceManager rm = prog.getReferenceManager();
int id = prog.startTransaction(testName.getMethodName());
try {
tx(program, () -> {
ReferenceManager rm = program.getReferenceManager();
Reference ref = rm.addMemoryReference(sample.getNewAddress(0x01004203),
sample.getNewAddress(0x51), RefType.UNCONDITIONAL_CALL, SourceType.USER_DEFINED, 0);
rm.setPrimary(ref, true);
}
finally {
prog.endTransaction(id, true);
}
});
waitForNotBusy(symbolTable);
row = symbolModel.getRowIndex(new SymbolRowObject(s));
row = symbolModel.getRowIndex(s);
refCount = getRefCount(row);
assertNotNull(refCount);
@ -755,36 +733,23 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testUpdateOnReferencesRemoved() throws Exception {
openProgram("sample");
Address sample = prog.getMinAddress();
Symbol s = getUniqueSymbol(prog, "doStuff");
int row = symbolModel.getRowIndex(new SymbolRowObject(s));
Address sample = program.getMinAddress();
Symbol s = getUniqueSymbol(program, "doStuff");
int row = symbolModel.getRowIndex(s);
Integer refCount = getRefCount(row);
assertNotNull(refCount);
assertEquals(4, refCount.intValue());
ReferenceManager rm = prog.getReferenceManager();
Reference[] refs = rm.getReferencesFrom(sample.getNewAddress(0x01004aea));
Address toAddr = sample.getNewAddress(0x50);
Reference ref = null;
for (Reference element : refs) {
if (toAddr.equals(element.getToAddress())) {
ref = element;
break;
}
}
if (ref == null) {
Assert.fail("Did not find expected mem reference!");
}
int id = prog.startTransaction(testName.getMethodName());
try {
rm.delete(ref);
}
finally {
prog.endTransaction(id, true);
}
Address from = sample.getNewAddress(0x01004aea);
Address to = sample.getNewAddress(0x50);
Reference ref = getReference(from, to);
tx(program, () -> {
ReferenceManager manager = program.getReferenceManager();
manager.delete(ref);
});
waitForNotBusy(symbolTable);
refCount = getRefCount(row);
@ -792,34 +757,44 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(3, refCount.intValue());
}
private Reference getReference(Address from, Address to) {
ReferenceManager rm = program.getReferenceManager();
Reference[] refs = rm.getReferencesFrom(from);
for (Reference element : refs) {
if (to.equals(element.getToAddress())) {
return element;
}
}
fail("Did not find expected mem reference between " + from + " and " + to);
return null;
}
@Test
public void testUpdateOnProgramRestore() throws Exception {
openProgram("sample");
int id = prog.startTransaction(testName.getMethodName());
try {
ClearCmd cmd = new ClearCmd(prog.getMemory(), new ClearOptions());
tool.execute(cmd, prog);
int startRowCount = symbolTable.getRowCount();
ClearCmd cmd = new ClearCmd(program.getMemory(), new ClearOptions());
applyCmd(program, cmd);
waitForBusyTool(tool);
}
finally {
prog.endTransaction(id, true);
}
waitForNotBusy(symbolTable);
// Externals are not cleared
int clearedRowCount = 3;
assertEquals(clearedRowCount, symbolTable.getRowCount());
assertEquals(3, symbolTable.getRowCount());
undo(prog);
undo(program);
waitForNotBusy(symbolTable);
assertEquals(24, symbolTable.getRowCount());
assertEquals(startRowCount, symbolTable.getRowCount());
redo(prog);
redo(program);
waitForNotBusy(symbolTable);
assertEquals(3, symbolTable.getRowCount());
assertEquals(clearedRowCount, symbolTable.getRowCount());
}
@Test
@ -827,14 +802,13 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
openProgram("winword.exe");
showFilterDialog();
final FilterDialog filterDialog = waitForDialogComponent(FilterDialog.class);
FilterDialog filterDialog = waitForDialogComponent(FilterDialog.class);
assertNotNull(filterDialog);
runSwing(() -> {
final NewSymbolFilter filter = new NewSymbolFilter();
NewSymbolFilter filter = new NewSymbolFilter();
turnOffAllFilterTypes(filter);
filter.setFilter("Function Labels", true);
filterDialog.setFilter(filter);
});
pressButtonByText(filterDialog, "OK");
@ -1104,7 +1078,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
JTextField textField = getFilterTextField();
// setup labels in the program for matching
waitForNotBusy(symbolTable);
int rowCount = symbolTable.getRowCount();
addLabel("bob", null, addr("010058f6"));
@ -1142,10 +1115,32 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
modelMatchesIgnoringCase("bob");
}
@Test
public void testReferenceRemvoed_ReferenceToDynamicSymbol() throws Exception {
openProgram("sample");
int row = findRow("DAT_00000006");
assertTrue(row > -1);
removeReference("0x00000005", "0x00000006");
row = findRow("DAT_00000006");
assertFalse(row > -1);
}
//==================================================================================================
// Helper methods
//==================================================================================================
private void removeReference(String from, String to) {
ReferenceManager rm = program.getReferenceManager();
Reference ref = rm.getReference(addr(from), addr(to), 0);
RemoveReferenceCmd cmd = new RemoveReferenceCmd(ref);
applyCmd(program, cmd);
}
private void assertMenuContains(List<JMenuItem> popupItems, String string) {
for (JMenuItem item : popupItems) {
String text = item.getText();
@ -1176,15 +1171,28 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
private void selectRow(int row) {
selectRow(row, row);
selectRows(row, row);
int selectedRow = symbolTable.getSelectedRow();
assertEquals("Row was not selected!", row, selectedRow);
waitForSwing();
}
private void selectRow(int start, int end) {
private void selectRows(int... rows) {
assertNotNull(rows);
assertTrue("Must have at least one row to select", rows.length > 0);
runSwing(() -> {
symbolTable.setRowSelectionInterval(start, end);
symbolTable.clearSelection();
for (int row : rows) {
symbolTable.addRowSelectionInterval(row, row);
}
int end = rows[rows.length - 1];
Rectangle rect = symbolTable.getCellRect(end, 0, true);
symbolTable.scrollRectToVisible(rect);
});
waitForSwing();
}
private FilterDialog showFilterDialog() {
@ -1226,7 +1234,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
filter.setFilter("Non-Primary Labels", active);
}
private void triggerAutoLookup(String text) {
private void triggerAutoLookup(String text) throws Exception {
KeyListener listener = (KeyListener) getInstanceField("autoLookupListener", symbolTable);
@ -1240,24 +1248,16 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
// use the version of triggerText that allows us to consume the event directly, bypassing
// the focus system
triggerText(symbolTable, text, consumer);
waitForNotBusy(symbolTable);
}
private void setName(Symbol symbol, String name, SourceType type) throws Exception {
int startTransaction = prog.startTransaction("Test");
try {
symbol.setName(name, SourceType.DEFAULT);
}
finally {
prog.endTransaction(startTransaction, true);
}
waitForSwing();
tx(program, () -> symbol.setName(name, SourceType.DEFAULT));
waitForNotBusy(symbolTable);
}
private Symbol getSymbol(int row) {
SymbolRowObject rowObject = symbolModel.getRowObject(row);
return rowObject.getSymbol();
return symbolModel.getRowObject(row);
}
private Integer getRefCount(int row) {
@ -1366,30 +1366,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
return -1;
}
//
// private void createFunctionWithDefaultParameters(Address addr) {
// CreateFunctionCmd cmd =
// new CreateFunctionCmd(null, addr, null, SourceType.DEFAULT, false, true);
// int transactionID = prog.startTransaction("TestCreateFunction");
// try {
// boolean success = tool.execute(cmd, prog);
//// boolean success = cmd.applyTo(prog);
// if (!success) {
// Assert.fail("Unexpectedly could not create a function");
// }
// }
// finally {
// prog.endTransaction(transactionID, true);
// }
//
// prog.flushEvents();
// waitForBusyTool(tool);
//
// FunctionManager functionManager = prog.getFunctionManager();
// Function function = functionManager.getFunctionAt(addr);
// assertNotNull(function);
// }
private ProgramLocation getProgramLocation(int row, int column, TableModel model) {
ProgramTableModel programModel = (ProgramTableModel) model;
return programModel.getProgramLocation(row, column);
@ -1399,9 +1375,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Namespace namespace = null;
if (namespaceName != null) {
Command command = new CreateNamespacesCmd(namespaceName, SourceType.USER_DEFINED);
if (tool.execute(command, prog)) {
if (tool.execute(command, program)) {
List<Namespace> namespaces =
NamespaceUtils.getNamespaces(namespaceName, null, prog);
NamespaceUtils.getNamespaces(namespaceName, null, program);
if (namespaces.size() != 1) {
Assert.fail("Unable to find the newly created parent namespace.");
@ -1411,12 +1387,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
Command command = new AddLabelCmd(address, label, namespace, SourceType.USER_DEFINED);
tool.execute(command, prog);
tool.execute(command, program);
waitForNotBusy(symbolTable);
}
private Address addr(String address) {
return prog.getAddressFactory().getAddress(address);
return program.getAddressFactory().getAddress(address);
}
private void myTypeText(Component c, String text) throws Exception {
@ -1482,7 +1458,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
private void waitForNotBusy(GTable table) throws Exception {
waitForProgram(prog);
waitForProgram(program);
ThreadedTableModel<?, ?> model = (ThreadedTableModel<?, ?>) table.getModel();
waitForTableModel(model);
}
@ -1490,7 +1466,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private void openProgram(String name) throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder(name, true);
prog = builder.getProgram();
program = builder.getProgram();
builder.createMemory("test0", "1", 0x100);
builder.createMemory("test1", "0x01001000", 0x1000);
@ -1543,7 +1519,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
builder.createLabel("0x80", "_SUB_010059A3");
// a generic function with params for testing
ParameterImpl p = new ParameterImpl(null, new ByteDataType(), prog);
ParameterImpl p = new ParameterImpl(null, new ByteDataType(), program);
builder.createEmptyFunction("func_with_parms", "0x100", 10, new Undefined1DataType(), p, p);
// references to these symbols
@ -1567,11 +1543,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
// data from ghidra
builder.createMemoryReadReference(add(ghidra, 4), add(ghidra, 6));
builder.createMemoryReadReference(add(ghidra, 5), add(ghidra, 7));
builder.createMemoryReadReference("0x00000005", "0x00000006");
// for testing navigation
builder.addBytesNOP(doStuff, 1);
env.showTool(prog);
env.showTool(program);
setUpSymbolTable();
}
@ -1608,8 +1585,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForNotBusy(symbolTable);
symbolTableHeader = symbolTable.getTableHeader();
sortAscending(SymbolTableModel.LABEL_COL);
}
@ -1623,11 +1598,10 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
@SuppressWarnings("unchecked")
private GhidraTableFilterPanel<SymbolRowObject> getFilterPanel() {
private GhidraTableFilterPanel<Symbol> getFilterPanel() {
Object symProvider = getInstanceField("symProvider", plugin);
Object panel = getInstanceField("symbolPanel", symProvider);
return (GhidraTableFilterPanel<SymbolRowObject>) getInstanceField("tableFilterPanel",
panel);
return (GhidraTableFilterPanel<Symbol>) getInstanceField("tableFilterPanel", panel);
}
private void singleClick(final JTable table, final int row, final int col) throws Exception {
@ -1655,18 +1629,20 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
tool1.addPlugin(SymbolTablePlugin.class.getName());
}
private int findRow(String symbolName) {
return findRow(symbolName, "Global");
}
private int findRow(String symbolName, String namespace) {
int max = symbolTable.getRowCount();
for (int i = 0; i < max; i++) {
SymbolTableNameValue symbolNameValue =
(SymbolTableNameValue) symbolTable.getValueAt(i, SymbolTableModel.LABEL_COL);
Symbol s = symbolNameValue.getSymbol();
Symbol s = (Symbol) symbolTable.getValueAt(i, SymbolTableModel.LABEL_COL);
if (symbolName.equals(s.getName()) &&
namespace.equals(s.getParentNamespace().getName())) {
return i;
}
}
Assert.fail("Symbol cell not found: " + namespace + "::" + symbolName);
return -1;
}

View file

@ -20,8 +20,7 @@ import java.util.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableModel;
import docking.widgets.table.sort.DefaultColumnComparator;
import docking.widgets.table.sort.RowToColumnComparator;
import docking.widgets.table.sort.*;
import ghidra.util.Swing;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@ -331,7 +330,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
*/
protected Comparator<T> createSortComparator(int columnIndex) {
return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(),
new StringBasedBackupRowToColumnComparator(columnIndex));
new StringBasedBackupRowToColumnComparator());
}
private Comparator<T> createLastResortComparator(ComparatorLink parentChain) {
@ -470,22 +469,16 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
}
}
private class StringBasedBackupRowToColumnComparator implements Comparator<T> {
private int sortColumn;
StringBasedBackupRowToColumnComparator(int sortColumn) {
this.sortColumn = sortColumn;
}
private class StringBasedBackupRowToColumnComparator implements BackupColumnComparator<T> {
@Override
public int compare(T t1, T t2) {
public int compare(T t1, T t2, Object c1, Object c2) {
if (t1 == t2) {
return 0;
}
String s1 = getColumStringValue(t1);
String s2 = getColumStringValue(t2);
String s1 = getColumStringValue(c1);
String s2 = getColumStringValue(c2);
if (s1 == null || s2 == null) {
return TableComparators.compareWithNullValues(s1, s2);
@ -494,11 +487,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
return s1.compareToIgnoreCase(s2);
}
private String getColumStringValue(T t) {
private String getColumStringValue(Object columnValue) {
// just use the toString(), which may or may not produce a good value (this will
// catch the cases where the column value is itself a string)
Object o = getColumnValueForRow(t, sortColumn);
return o == null ? null : o.toString();
return columnValue == null ? null : columnValue.toString();
}
}
}

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.
@ -34,6 +33,7 @@ public class AddRemoveListItem<T> {
public boolean isRemove() {
return isRemove;
}
public boolean isChange() {
return isAdd && isRemove;
}
@ -41,4 +41,16 @@ public class AddRemoveListItem<T> {
public T getValue() {
return value;
}
@Override
public String toString() {
//@formatter:off
return "{\n" +
"\tvalue: " + value +",\n" +
"\tisAdd: " + isAdd +",\n" +
"\tisRemove: " + isRemove +"\n" +
"}";
//@formatter:on
}
}

View file

@ -441,7 +441,6 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
}
return column.getValue(t, columnSettings.get(column), dataSource, serviceProvider);
}
/**

View file

@ -0,0 +1,50 @@
/* ###
* 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 docking.widgets.table.sort;
import java.util.Comparator;
/**
* An interface that is conceptually the same as a {@link Comparator}. The only difference is
* that we pass the row objects <b>and</b> the column values to
* {@link #compare(Object, Object, Object, Object)}. This allows us to take advantage of
* already-retrieved column values. This can speed-up table sorting, as repeatedly retrieving
* column values for each comparison is slow.
*
* @param <T> the row type
*/
public interface BackupColumnComparator<T> {
static final BackupColumnComparator<Object> NO_SORT_COMPARATOR = (t1, t2, o1, o2) -> 0;
@SuppressWarnings("unchecked") // we are casting to Object; safe since no comparisons are done
public static <T> BackupColumnComparator<T> getNoSortComparator() {
return (BackupColumnComparator<T>) NO_SORT_COMPARATOR;
}
/**
* Compares two row/column values using the same contract as
* {@link Comparator#compare(Object, Object)}
*
* @param t1 the 1st row object
* @param t2 the 2nd row object
* @param c1 the 1st column value
* @param c2 the second column value
* @return 0 if the 2 values compare the same; negative if the first value compares less than
* the second; positive if the first value compares as larger than the first
*/
public int compare(T t1, T t2, Object c1, Object c2);
}

View file

@ -15,8 +15,6 @@
*/
package docking.widgets.table.sort;
import java.util.Comparator;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.GColumnRenderer;
@ -29,7 +27,7 @@ import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
*
* @param <T> the row type
*/
public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T> {
public class ColumnRenderedValueBackupRowComparator<T> implements BackupColumnComparator<T> {
protected int sortColumn;
protected DynamicColumnTableModel<T> model;
@ -56,13 +54,13 @@ public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T>
}
@Override
public int compare(T t1, T t2) {
public int compare(T t1, T t2, Object c1, Object c2) {
if (t1 == t2) {
return 0;
}
String s1 = getRenderedColumnStringValue(t1);
String s2 = getRenderedColumnStringValue(t2);
String s1 = getRenderedColumnStringValue(c1);
String s2 = getRenderedColumnStringValue(c2);
if (s1 == null || s2 == null) {
return TableComparators.compareWithNullValues(s1, s2);
@ -75,7 +73,7 @@ public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T>
// unsafe. We happen know that we retrieved the value from the column that we are passing
// it to, so the casting and usage is indeed safe.
@SuppressWarnings("unchecked")
private String getRenderedColumnStringValue(T t) {
private String getRenderedColumnStringValue(Object columnValue) {
if (!supportsColumnSorting) {
return null;
@ -83,13 +81,12 @@ public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T>
DynamicTableColumn<T, ?, ?> column = model.getColumn(sortColumn);
GColumnRenderer<Object> renderer = (GColumnRenderer<Object>) column.getColumnRenderer();
Object o = getColumnValue(t);
if (renderer == null) {
return o == null ? null : o.toString();
return columnValue == null ? null : columnValue.toString();
}
Settings settings = model.getColumnSettings(sortColumn);
return renderer.getFilterString(o, settings);
return renderer.getFilterString(columnValue, settings);
}
// this may be overridden to use caching

View file

@ -32,7 +32,8 @@ public class RowToColumnComparator<T> implements Comparator<T> {
protected RowObjectTableModel<T> model;
protected int sortColumn;
protected Comparator<Object> columnComparator;
protected Comparator<T> backupRowComparator = TableComparators.getNoSortComparator();
protected BackupColumnComparator<T> backupRowComparator =
BackupColumnComparator.getNoSortComparator();
/**
* Constructs this class with the given column comparator that will get called after the
@ -60,7 +61,7 @@ public class RowToColumnComparator<T> implements Comparator<T> {
* @param backupRowComparator the backup row comparator
*/
public RowToColumnComparator(RowObjectTableModel<T> model, int sortColumn,
Comparator<Object> comparator, Comparator<T> backupRowComparator) {
Comparator<Object> comparator, BackupColumnComparator<T> backupRowComparator) {
this.model = model;
this.sortColumn = sortColumn;
this.columnComparator = Objects.requireNonNull(comparator);
@ -97,7 +98,7 @@ public class RowToColumnComparator<T> implements Comparator<T> {
// backup comparator is not a stub and will do something reasonable for the sort,
// depending upon how the model created this class.
//
return backupRowComparator.compare(t1, t2);
return backupRowComparator.compare(t1, t2, value1, value2);
}
protected Object getColumnValue(T t) {

View file

@ -17,6 +17,7 @@ package docking.widgets.table.threaded;
import java.util.Comparator;
import docking.widgets.table.sort.BackupColumnComparator;
import docking.widgets.table.sort.RowToColumnComparator;
/**
@ -55,7 +56,7 @@ public class ThreadedTableColumnComparator<T> extends RowToColumnComparator<T> {
* @see RowToColumnComparator
*/
public ThreadedTableColumnComparator(ThreadedTableModel<T, ?> model, int sortColumn,
Comparator<Object> comparator, Comparator<T> backupRowComparator) {
Comparator<Object> comparator, BackupColumnComparator<T> backupRowComparator) {
super(model, sortColumn, comparator, backupRowComparator);
this.threadedModel = model;
}

View file

@ -214,7 +214,37 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
protected abstract void doLoad(Accumulator<ROW_OBJECT> accumulator, TaskMonitor monitor)
throws CancelledException;
/**
* This method will retrieve a column value for the given row object. Further, the retrieved
* value will be cached. This is useful when sorting a table, as the same column value may
* be requested multiple times.
*
* <p><u>Performance Notes</u>
* <ul>
* <li>This method uses a {@link HashMap} to cache column values for a row object. Further,
* upon a key collision, the map will perform O(logn) lookups <b>if the
* key (the row object) is {@link Comparable}</b>. If the key is not comparable, then
* the collision lookups will be linear. So, make your row objects comparable
* for maximum speed <b>when your table size becomes large</b> (for small tables there
* is no observable impact).
* <li>Even if your row objects are comparable, relying on this table model to convert your
* row object into column values can be slow <b>for large tables</b>. This is because
* the default column comparison framework for the tables will call this method
* multiple times, resulting in many more method calls per column value lookup. For
* large data, the repeated method calls start to become noticeable. For maximum
* column sorting speed, use a comparator that works not on the column value, but on
* the row value. To do this, return a comparator from your model's
* {@link #createSortComparator(int)} method, instead of from the column itself or
* by relying on column item implementing {@link Comparable}. This is possible any
* time that a row object already has a field that is used for a given column.
* </ul>
*
* @param rowObject the row object
* @param columnIndex the column index for which to get a value
* @return the column value
*/
Object getCachedColumnValueForRow(ROW_OBJECT rowObject, int columnIndex) {
Map<ROW_OBJECT, Map<Integer, Object>> cachedColumnValues = threadLocalColumnCache.get();
if (cachedColumnValues == null) {

View file

@ -58,7 +58,7 @@ public class TestThread extends Thread {
/**
* Returns true if the given thread name is the test thread name
*
* @param t the thread name to check
* @param name the thread name to check
* @return true if the given thread name is the test thread name
*/
public static boolean isTestThreadName(String name) {

View file

@ -48,7 +48,7 @@ public class LRUMap<K, V> implements Map<K, V> {
protected HashMap<K, Entry<K, V>> map;
private int cacheSize;
private Entry<K, V> head;
private long modificationID = 0;
private volatile long modificationID = 0;
public LRUMap(int cacheSize) {
this.cacheSize = cacheSize;
@ -267,7 +267,7 @@ public class LRUMap<K, V> implements Map<K, V> {
/**
* This is called after an item has been removed from the cache.
* @param eldest the ite being removed
* @param eldest the item being removed
*/
protected void eldestEntryRemoved(Map.Entry<K, V> eldest) {
// this is just a way for subclasses to know when items are removed from the cache

View file

@ -217,7 +217,8 @@ public class CodeSymbol extends SymbolDB {
*/
@Override
public boolean isValidParent(Namespace parent) {
return SymbolType.LABEL.isValidParent(symbolMgr.getProgram(), parent, address, isExternal());
return SymbolType.LABEL.isValidParent(symbolMgr.getProgram(), parent, address,
isExternal());
// if (isExternal() != parent.isExternal()) {
// return false;
@ -241,15 +242,12 @@ public class CodeSymbol extends SymbolDB {
// return true;
}
/**
* @see ghidra.program.model.symbol.Symbol#getName()
*/
@Override
public String getName() {
protected String doGetName() {
if (getSource() == SourceType.DEFAULT && isExternal()) {
return ExternalManagerDB.getDefaultExternalName(this);
}
return super.getName();
return super.doGetName();
}
@Override

View file

@ -33,7 +33,6 @@ import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
/**
* Symbol class for functions.
@ -237,11 +236,8 @@ public class FunctionSymbol extends SymbolDB {
isExternal());
}
/**
* @see ghidra.program.model.symbol.Symbol#getName()
*/
@Override
public String getName() {
protected String doGetName() {
if (getSource() == SourceType.DEFAULT) {
if (isExternal()) {
return ExternalManagerDB.getDefaultExternalName(this);
@ -251,17 +247,17 @@ public class FunctionSymbol extends SymbolDB {
Symbol thunkedSymbol = getThunkedSymbol();
if (thunkedSymbol instanceof FunctionSymbol) {
FunctionSymbol thunkedFuncSym = (FunctionSymbol) thunkedSymbol;
String name = thunkedFuncSym.getName();
String thunkName = thunkedFuncSym.getName();
if (thunkedFuncSym.getSource() == SourceType.DEFAULT &&
thunkedFuncSym.getThunkedSymbol() == null) {
// if thunking a default non-thunk function
name = "thunk_" + name;
thunkName = "thunk_" + thunkName;
}
return name;
return thunkName;
}
return SymbolUtilities.getDefaultFunctionName(address);
}
return super.getName();
return super.doGetName();
}
// @Override
@ -363,7 +359,7 @@ public class FunctionSymbol extends SymbolDB {
checkIsValid();
Reference[] refs = super.getReferences(monitor);
if (monitor == null) {
monitor = TaskMonitorAdapter.DUMMY_MONITOR;
monitor = TaskMonitor.DUMMY;
}
if (monitor.isCancelled()) {
return refs;

View file

@ -54,7 +54,7 @@ public class GlobalVariableSymbolDB extends VariableSymbolDB {
}
@Override
public String getName() {
protected String doGetName() {
if (!checkIsValid()) {
// TODO: SCR
return "[Invalid VariableSymbol - Deleted!]";
@ -63,7 +63,7 @@ public class GlobalVariableSymbolDB extends VariableSymbolDB {
if (storage == null) {
return Function.DEFAULT_LOCAL_PREFIX + "_!BAD!";
}
return super.getName();
return super.doGetName();
}
}

View file

@ -30,11 +30,13 @@ import ghidra.program.model.listing.CircularDependencyException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ChangeManager;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Lock;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.*;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
/**
* Base class for symbols
@ -43,10 +45,25 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
private Record record;
private boolean isDeleting = false;
protected String name;
protected Address address;
protected SymbolManager symbolMgr;
protected Lock lock;
/**
* Creates a Symbol that is just a placeholder for use when trying to find symbols by using
* {@link Symbol#getID()}. This is useful for locating symbols in Java collections when
* a symbol has been deleted and the only remaining information is that symbol's ID.
*
* @param manager the manager for the new symbol
* @param address the address of the symbol
* @param id the id of the symbol
* @return the fake symbol
*/
static SymbolDB createSymbolPlaceholder(SymbolManager manager, Address address, long id) {
return new PlaceholderSymbolDB(manager, address, id);
}
SymbolDB(SymbolManager symbolMgr, DBObjectCache<SymbolDB> cache, Address address,
Record record) {
super(cache, record.getKey());
@ -65,6 +82,11 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
@Override
public String toString() {
// prefer cached name for speed; it may be stale; call getName() for current value
String temp = name;
if (temp != null) {
return temp;
}
return getName();
}
@ -75,6 +97,7 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
@Override
protected boolean refresh(Record rec) {
name = null;
if (record != null) {
if (rec == null) {
rec = symbolMgr.getSymbolRecord(key);
@ -145,17 +168,29 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
lock.acquire();
try {
checkIsValid();
if (record != null) {
return record.getString(SymbolDatabaseAdapter.SYMBOL_NAME_COL);
if (name == null) {
name = doGetName();
}
return SymbolUtilities.getDynamicName(symbolMgr.getProgram(), address);
return name;
}
finally {
lock.release();
}
}
/**
* The code for creating the name content for this symbol. This code will be called
* with the symbol's lock.
*
* @return the name
*/
protected String doGetName() {
if (record != null) {
return record.getString(SymbolDatabaseAdapter.SYMBOL_NAME_COL);
}
return SymbolUtilities.getDynamicName(symbolMgr.getProgram(), address);
}
@Override
public Program getProgram() {
return symbolMgr.getProgram();
@ -238,7 +273,7 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
try {
checkIsValid();
if (monitor == null) {
monitor = TaskMonitorAdapter.DUMMY_MONITOR;
monitor = TaskMonitor.DUMMY;
}
if (monitor.getMaximum() == 0) {
@ -275,7 +310,7 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
@Override
public Reference[] getReferences() {
return getReferences(TaskMonitorAdapter.DUMMY_MONITOR);
return getReferences(TaskMonitor.DUMMY);
}
@Override
@ -470,7 +505,11 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
/**
* Allow symbol implementations to validate the source when setting the name of
* this symbol.
* this symbol
*
* @param newName the new name
* @param source the source type
* @return the validated source type
*/
protected SourceType validateNameSource(String newName, SourceType source) {
return source;
@ -482,6 +521,7 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
lock.acquire();
try {
name = null;
checkDeleted();
checkEditOK();
@ -535,6 +575,7 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
record.setLongValue(SymbolDatabaseAdapter.SYMBOL_PARENT_COL, newNamespace.getID());
record.setString(SymbolDatabaseAdapter.SYMBOL_NAME_COL, newName);
name = newName;
updateSymbolSource(record, source);
updateRecord();
@ -613,10 +654,16 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
if (obj == this) {
return true;
}
Symbol s = (Symbol) obj;
if (getID() == s.getID()) {
return true;
}
if (!getName().equals(s.getName())) {
return false;
}
if (!getAddress().equals(s.getAddress())) {
return false;
}
@ -776,6 +823,7 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
/**
* gets the generic symbol data 2 data.
* @return the symbol data
*/
public int getSymbolData2() {
lock.acquire();
@ -852,10 +900,64 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
/**
* Change the record and key associated with this symbol
* @param the record.
* @param record the record
*/
void setRecord(Record record) {
this.record = record;
keyChanged(record.getKey());
}
private static class PlaceholderSymbolDB extends SymbolDB {
PlaceholderSymbolDB(SymbolManager symbolMgr, Address address, long key) {
super(symbolMgr, null, address, key);
}
@Override
public boolean equals(Object obj) {
if ((obj == null) || (!(obj instanceof Symbol))) {
return false;
}
if (obj == this) {
return true;
}
// this class is only ever equal if the id matches
Symbol s = (Symbol) obj;
if (getID() == s.getID()) {
return true;
}
return false;
}
@Override
public SymbolType getSymbolType() {
throw new IllegalArgumentException();
}
@Override
public ProgramLocation getProgramLocation() {
throw new IllegalArgumentException();
}
@Override
public boolean isExternal() {
throw new IllegalArgumentException();
}
@Override
public Object getObject() {
throw new IllegalArgumentException();
}
@Override
public boolean isPrimary() {
throw new IllegalArgumentException();
}
@Override
public boolean isValidParent(Namespace parent) {
throw new IllegalArgumentException();
}
}
}

View file

@ -2274,6 +2274,11 @@ public class SymbolManager implements SymbolTable, ManagerDB {
return new NamespaceDB(s, namespaceMgr);
}
@Override
public Symbol createSymbolPlaceholder(Address address, long id) {
return SymbolDB.createSymbolPlaceholder(this, address, id);
}
/**
* Creates a symbol, specifying all information for the record. This method is not on the
* public interface and is only intended for program API internal use. The user of this
@ -2675,5 +2680,4 @@ class SymbolMatcher implements Predicate<Symbol> {
SymbolType type = s.getSymbolType();
return type == type1;
}
}

View file

@ -203,7 +203,7 @@ public class VariableSymbolDB extends SymbolDB {
}
@Override
public String getName() {
protected String doGetName() {
if (!checkIsValid()) {
// TODO: SCR
return "[Invalid VariableSymbol - Deleted!]";
@ -213,7 +213,7 @@ public class VariableSymbolDB extends SymbolDB {
if (getSource() == SourceType.DEFAULT) {
return getParamName();
}
String storedName = super.getName();
String storedName = super.doGetName();
if (SymbolUtilities.isDefaultParameterName(storedName)) {
return getParamName();
}
@ -232,7 +232,7 @@ public class VariableSymbolDB extends SymbolDB {
// TODO: we use to check for a default name and regenerate new default name but we should
// not need to do this if source remains at default
return super.getName();
return super.doGetName();
}
@Override

View file

@ -543,4 +543,15 @@ public interface SymbolTable {
public Namespace createNameSpace(Namespace parent, String name, SourceType source)
throws DuplicateNameException, InvalidInputException;
/**
* Creates a Symbol that is just a placeholder for use when trying to find symbols by using
* {@link Symbol#getID()}. This is useful for locating symbols in Java collections when
* a symbol has been deleted and the only remaining information is that symbol's ID.
*
* @param address the address of the symbol
* @param id the id of the symbol
* @return the fake symbol
*/
public Symbol createSymbolPlaceholder(Address address, long id);
}

View file

@ -897,7 +897,11 @@ public class SymbolUtilities {
if (symbol.isExternal()) {
return "External Function";
}
Function func = (Function) symbol.getObject();
if (func == null) {
return null; // symbol deleted
}
if (func.isThunk()) {
return "Thunk Function";
}