diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/images/Symbol_Table.png b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/images/Symbol_Table.png index ac1044343a..5d9ca77180 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/images/Symbol_Table.png and b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/images/Symbol_Table.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table.htm index 8dcf07cf59..dcbfc7265f 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table.htm @@ -31,7 +31,7 @@
-

The columns in the table are:

+

Some of the columns in the table are:

@@ -54,13 +54,6 @@ - - - - - - @@ -89,7 +82,15 @@

You can sort the table on any column by clicking on the column header. The column can be sorted in ascending or descending order.

-

The colors for bad +

+

+ Sorting a column in the symbol table when the program has a large number of symbols + can be slow. If you do not need sorting, then you can control-click the sorted column + to remove the sort. +

+
+ +

The colors for bad references, entry points, dead code, offcut code, function names, local symbols, The Name Only checkbox allows you to toggle whether to filter on only the name column or all the columns in the table.

+ +
+

+ Filtering the symbol table when the program has a large number of symbols can be slow. + When only filtering on the symbol name, via the checkbox above, the overall filtering + is considerably faster. +

+
+

The filter text field will accept basic globbing characters such as '*' and '?' within the filter text unless the "Regular Expression" filter strategy is diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Tables/GhidraTableHeaders.html b/Ghidra/Features/Base/src/main/help/help/topics/Tables/GhidraTableHeaders.html index 54d179103e..4a11d1824c 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Tables/GhidraTableHeaders.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/Tables/GhidraTableHeaders.html @@ -83,7 +83,8 @@

To remove a sort column from a multiple column sort, Ctrl-left-click that column. This will even work when only one column is sorted, thus effectively disabling - sorting for the table. + sorting for the table. Disabling sorting can greatly increase the + table's performance when the number of rows is large

@@ -212,5 +213,11 @@

+

Related Topics

+ + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Trees/GhidraTreeFilter.html b/Ghidra/Features/Base/src/main/help/help/topics/Trees/GhidraTreeFilter.html index 0f91134ed2..b7a8651a04 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Trees/GhidraTreeFilter.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/Trees/GhidraTreeFilter.html @@ -15,7 +15,11 @@

Most trees and tables in Ghidra support filtering and have a text filter located at the - bottom of the tree or table. Below is an example from the Data Type Manager. The filter is + bottom of the tree or table. Tables also support the concept of + Column Filters described below. +

+ +

Below is an example from the Data Type Manager. The filter is currently set to "Starts With", but you can select a different filter strategy.

@@ -270,6 +274,17 @@

Most filterable tables in Ghidra support advanced filtering based on column values. This allows for complex filtering where you can logically combine column specific clauses.

+ + +
+

+ Some columns in tables are not filterable via the filter text field below the table. + For example, many numeric columns are ignored by the text filter because they can be + slow to calculate and they are better filtered by using a range filter, as is available + using column filters. +

+
+
@@ -458,5 +473,20 @@ filter.

+ + + +
+
+
+
+ +

Related Topics

+ + + + diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/RenameLabelCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/RenameLabelCmd.java index 54aa3fe11c..211a3e982d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/RenameLabelCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/RenameLabelCmd.java @@ -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; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java index 2bb3913102..4c7a776b2a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java @@ -16,80 +16,34 @@ package ghidra.app.context; import java.awt.Component; -import java.util.Iterator; -import java.util.NoSuchElementException; +import java.util.*; import docking.ComponentProvider; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; -import ghidra.program.model.symbol.SymbolIterator; public class ProgramSymbolActionContext extends ProgramActionContext { - private final long[] symbolIDs; + private List symbols = new ArrayList(); - public ProgramSymbolActionContext(ComponentProvider provider, Program program, long[] symbolIDs, - Component sourceComponent) { + public ProgramSymbolActionContext(ComponentProvider provider, Program program, + List symbols, Component sourceComponent) { super(provider, program, sourceComponent); - this.symbolIDs = symbolIDs; + this.symbols = symbols == null ? Collections.emptyList() : symbols; } public int getSymbolCount() { - return symbolIDs != null ? symbolIDs.length : 0; + return symbols.size(); } public Symbol getFirstSymbol() { - if (symbolIDs == null || symbolIDs.length == 0) { + if (symbols.isEmpty()) { return null; } - return program.getSymbolTable().getSymbol(symbolIDs[0]); + return symbols.get(0); } - public SymbolIterator getSymbols() { - return new MySymbolIterator(); - } - - private class MySymbolIterator implements SymbolIterator { - - private int index = -1; - private Symbol symbol = null; - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - return this; - } - - @Override - public boolean hasNext() { - if (symbol != null) { - return true; - } - if (symbolIDs == null) { - return false; - } - while (index < (symbolIDs.length - 1)) { - symbol = program.getSymbolTable().getSymbol(symbolIDs[++index]); - if (symbol != null) { - return true; - } - } - return false; - } - - @Override - public Symbol next() { - if (hasNext()) { - Symbol s = symbol; - symbol = null; - return s; - } - throw new NoSuchElementException(); - } - + public Iterable getSymbols() { + return symbols; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java index 50c2ab0ac4..0f0b6ae1e4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,42 +15,51 @@ */ package ghidra.app.plugin.core.functionwindow; +import ghidra.program.model.listing.Function; + class FunctionRowObject implements Comparable { - private final long key; + private final Function function; - FunctionRowObject(long key) { - this.key = key; + FunctionRowObject(Function function) { + this.function = function; + } + + Function getFunction() { + return function; } @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (key ^ (key >>> 32)); - return result; + return (int) function.getID(); } @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } + + long key = function.getID(); FunctionRowObject other = (FunctionRowObject) obj; - if (key != other.key) + if (key != other.function.getID()) { return false; + } return true; } long getKey() { - return key; + return function.getID(); } @Override public int compareTo(FunctionRowObject o) { - return ((Long) key).compareTo(o.key); + return ((Long) function.getID()).compareTo(o.function.getID()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToAddressTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToAddressTableRowMapper.java index 85c7b8b319..2ac2bbda88 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToAddressTableRowMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToAddressTableRowMapper.java @@ -17,16 +17,17 @@ package ghidra.app.plugin.core.functionwindow; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.util.table.ProgramLocationTableRowMapper; -public class FunctionRowObjectToAddressTableRowMapper extends - ProgramLocationTableRowMapper { +public class FunctionRowObjectToAddressTableRowMapper + extends ProgramLocationTableRowMapper { @Override - public Address map(FunctionRowObject rowObject, Program program, ServiceProvider serviceProvider) { - FunctionManager functionManager = program.getFunctionManager(); - Function function = functionManager.getFunction(rowObject.getKey()); + public Address map(FunctionRowObject rowObject, Program program, + ServiceProvider serviceProvider) { + Function function = rowObject.getFunction(); if (function == null) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToFunctionTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToFunctionTableRowMapper.java index 26138e3128..c09e22dc98 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToFunctionTableRowMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToFunctionTableRowMapper.java @@ -16,16 +16,18 @@ package ghidra.app.plugin.core.functionwindow; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.util.table.ProgramLocationTableRowMapper; -public class FunctionRowObjectToFunctionTableRowMapper extends - ProgramLocationTableRowMapper { +public class FunctionRowObjectToFunctionTableRowMapper + extends ProgramLocationTableRowMapper { @Override - public Function map(FunctionRowObject rowObject, Program program, ServiceProvider serviceProvider) { - FunctionManager functionManager = program.getFunctionManager(); - return functionManager.getFunction(rowObject.getKey()); + public Function map(FunctionRowObject rowObject, Program program, + ServiceProvider serviceProvider) { + Function function = rowObject.getFunction(); + return function; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToProgramLocationTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToProgramLocationTableRowMapper.java index ae44131afc..da87bb5038 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToProgramLocationTableRowMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObjectToProgramLocationTableRowMapper.java @@ -16,19 +16,19 @@ package ghidra.app.plugin.core.functionwindow; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.program.util.FunctionSignatureFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.util.table.ProgramLocationTableRowMapper; -public class FunctionRowObjectToProgramLocationTableRowMapper extends - ProgramLocationTableRowMapper { +public class FunctionRowObjectToProgramLocationTableRowMapper + extends ProgramLocationTableRowMapper { @Override public ProgramLocation map(FunctionRowObject rowObject, Program program, ServiceProvider serviceProvider) { - FunctionManager functionManager = program.getFunctionManager(); - Function function = functionManager.getFunction(rowObject.getKey()); + Function function = rowObject.getFunction(); if (function == null) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java index d14d79c1e4..43b87a3c40 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java @@ -17,7 +17,9 @@ package ghidra.app.plugin.core.functionwindow; import docking.widgets.table.DiscoverableTableUtils; import docking.widgets.table.TableColumnDescriptor; +import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.util.LongIterator; @@ -43,20 +45,19 @@ class FunctionTableModel extends AddressBasedTableModel { @Override protected TableColumnDescriptor createTableColumnDescriptor() { - TableColumnDescriptor descriptor = - new TableColumnDescriptor<>(); + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); - descriptor.addVisibleColumn(DiscoverableTableUtils.adaptColumForModel(this, - new LabelTableColumn())); + descriptor.addVisibleColumn(new NameTableColumn()); descriptor.addVisibleColumn( DiscoverableTableUtils.adaptColumForModel(this, new AddressTableColumn()), 1, true); - descriptor.addVisibleColumn(DiscoverableTableUtils.adaptColumForModel(this, - new FunctionSignatureTableColumn())); - //make function size a default column so that a user who wants to know the function size - //won't add the "Byte Count" column (which only display the number of bytes in the code - //unit at the function's entry point). - descriptor.addVisibleColumn(DiscoverableTableUtils.adaptColumForModel(this, - new FunctionBodySizeTableColumn())); + descriptor.addVisibleColumn( + DiscoverableTableUtils.adaptColumForModel(this, new FunctionSignatureTableColumn())); + + // Make function size a default column so that a user who wants to know the function size + // won't add the "Byte Count" column (which only display the number of bytes in the code + // unit at the function's entry point). + descriptor.addVisibleColumn( + DiscoverableTableUtils.adaptColumForModel(this, new FunctionBodySizeTableColumn())); // Function tag column is not something widely used, so make hidden by default descriptor.addHiddenColumn( @@ -91,13 +92,15 @@ class FunctionTableModel extends AddressBasedTableModel { if (functionMgr != null) { it = new FunctionKeyIterator(functionMgr); } + monitor.initialize(getKeyCount()); int progress = 0; while (it.hasNext()) { monitor.setProgress(progress++); monitor.checkCanceled(); long key = it.next(); - accumulator.add(new FunctionRowObject(key)); + Function f = functionMgr.getFunction(key); + accumulator.add(new FunctionRowObject(f)); } } @@ -133,16 +136,16 @@ class FunctionTableModel extends AddressBasedTableModel { } } - void functionAdded(Function function) { - addObject(new FunctionRowObject(function.getID())); + void functionAdded(Function f) { + addObject(new FunctionRowObject(f)); } - void functionRemoved(Function function) { - removeObject(new FunctionRowObject(function.getID())); + void functionRemoved(Function f) { + removeObject(new FunctionRowObject(f)); } - void update(Function function) { - updateObject(new FunctionRowObject(function.getID())); + void update(Function f) { + updateObject(new FunctionRowObject(f)); } @Override @@ -152,4 +155,25 @@ class FunctionTableModel extends AddressBasedTableModel { Function function = functionManager.getFunction(rowObject.getKey()); return function != null ? function.getEntryPoint() : null; } + + private class NameTableColumn + extends AbstractProgramBasedDynamicTableColumn { + + @Override + public String getColumnName() { + return "Name"; + } + + @Override + public String getValue(FunctionRowObject rowObject, Settings settings, Program data, + ServiceProvider sp) throws IllegalArgumentException { + + Function function = rowObject.getFunction(); + if (function == null) { + return null; + } + return function.getName(); + } + + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java index e3895a640c..ed009ebac2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +15,15 @@ */ package ghidra.app.plugin.core.symboltree; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.tree.TreePath; + import ghidra.app.context.ProgramSymbolActionContext; import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; import ghidra.program.model.listing.Program; - -import javax.swing.tree.TreePath; +import ghidra.program.model.symbol.Symbol; public class SymbolTreeActionContext extends ProgramSymbolActionContext { @@ -28,7 +31,7 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext { SymbolTreeActionContext(SymbolTreeProvider provider, Program program, SymbolGTree tree, TreePath[] selectionPaths) { - super(provider, program, getSymbolIDs(selectionPaths), tree); + super(provider, program, getSymbols(selectionPaths), tree); this.selectionPaths = selectionPaths; } @@ -51,23 +54,23 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext { return null; } - private static long[] getSymbolIDs(TreePath[] selectionPaths) { + private static List getSymbols(TreePath[] selectionPaths) { if (selectionPaths == null) { return null; } - long[] symbolIDs = new long[selectionPaths.length]; - int index = 0; + + List symbols = new ArrayList<>(); for (TreePath treePath : selectionPaths) { Object object = treePath.getLastPathComponent(); if (object instanceof SymbolNode) { SymbolNode symbolNode = (SymbolNode) object; - symbolIDs[index++] = symbolNode.getSymbolID(); + symbols.add(symbolNode.getSymbol()); } else { // Do not return symbols if selection contains non-symbolNodes return null; } } - return symbolIDs; + return symbols; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java index 097cd3002e..1ffb91758b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java @@ -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()); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java index 26856b44b2..ed53d041f1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,36 +15,38 @@ */ package ghidra.app.plugin.core.symtable; -import ghidra.program.model.symbol.Symbol; - import java.awt.Component; import javax.swing.*; +import ghidra.program.model.symbol.Symbol; + class SymbolEditor extends DefaultCellEditor { - private JTextField symbolField = null; + private JTextField symbolField = null; - SymbolEditor() { - super(new JTextField()); - symbolField = (JTextField)super.getComponent(); - symbolField.setBorder(BorderFactory.createEmptyBorder()); - } + SymbolEditor() { + super(new JTextField()); + symbolField = (JTextField) super.getComponent(); + symbolField.setBorder(BorderFactory.createEmptyBorder()); + } - @Override - public Object getCellEditorValue() { - return symbolField.getText().trim(); - } + @Override + public Object getCellEditorValue() { + return symbolField.getText().trim(); + } - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { - if (value instanceof Symbol) { - Symbol symbol = (Symbol) value; - symbolField.setText(symbol.getName()); - } - else { - symbolField.setText(""); - } - return symbolField; - } + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, + int row, int column) { + + Symbol symbol = (Symbol) value; + if (symbol != null) { + symbolField.setText(symbol.getName()); + } + else { + symbolField.setText(""); + } + return symbolField; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java index f651b9dbf9..1f09fcc85a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java @@ -37,7 +37,7 @@ import ghidra.util.table.*; class SymbolPanel extends JPanel { - private static final boolean FILTER_NAME_ONLY_DEFAULT = false; + private static final boolean FILTER_NAME_ONLY_DEFAULT = true; private static final String FILTER_SETTINGS_ELEMENT_NAME = "FILTER_SETTINGS"; @@ -46,15 +46,14 @@ class SymbolPanel extends JPanel { private GhidraTable symTable; private TableModelListener listener; private FilterDialog filterDialog; - private PluginTool tool; - private GhidraThreadedTablePanel threadedTablePanel; - private GhidraTableFilterPanel tableFilterPanel; + private GhidraThreadedTablePanel threadedTablePanel; + private GhidraTableFilterPanel tableFilterPanel; SymbolPanel(SymbolProvider provider, SymbolTableModel model, SymbolRenderer renderer, final PluginTool tool, GoToService gotoService) { super(new BorderLayout()); - this.tool = tool; + this.symProvider = provider; this.tableModel = model; @@ -117,9 +116,9 @@ class SymbolPanel extends JPanel { return tableFilterPanel; } - protected RowFilterTransformer updateRowDataTransformer(boolean nameOnly) { + protected RowFilterTransformer updateRowDataTransformer(boolean nameOnly) { if (nameOnly) { - return new NameOnlyRowTransformer(tableModel); + return new NameOnlyRowTransformer(); } return new DefaultRowFilterTransformer<>(tableModel, symTable.getColumnModel()); @@ -174,21 +173,19 @@ class SymbolPanel extends JPanel { if (selectedRowCount == 1) { int selectedRow = symTable.getSelectedRow(); - Object obj = symTable.getValueAt(selectedRow, - symTable.convertColumnIndexToView(SymbolTableModel.LABEL_COL)); - if (obj instanceof Symbol) { - symProvider.setCurrentSymbol((Symbol) obj); - return; - } + Symbol symbol = symProvider.getSymbolForRow(selectedRow); + symProvider.setCurrentSymbol(symbol); + } + else { + symProvider.setCurrentSymbol(null); } - symProvider.setCurrentSymbol(null); } int getActualSymbolCount() { return symTable.getRowCount(); } - List getSelectedSymbolKeys() { + List getSelectedSymbols() { int[] rows = symTable.getSelectedRows(); return tableModel.getRowObjects(rows); } @@ -201,20 +198,16 @@ class SymbolPanel extends JPanel { // Inner Classes //================================================================================================== - private static class NameOnlyRowTransformer implements RowFilterTransformer { + private static class NameOnlyRowTransformer implements RowFilterTransformer { private List list = new ArrayList<>(); - private SymbolTableModel model; - - NameOnlyRowTransformer(SymbolTableModel model) { - this.model = model; - } @Override - public List transform(SymbolRowObject rowObject) { + public List transform(Symbol rowObject) { list.clear(); - Symbol symbol = model.getSymbolForRowObject(rowObject); - if (symbol != null) { - list.add(symbol.getName()); + 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; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java index 3338669448..77ee04ffa0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java @@ -74,17 +74,13 @@ class SymbolProvider extends ComponentProviderAdapter { if (program == null) { return null; } - List rowObjects = symbolPanel.getSelectedSymbolKeys(); - long[] symbolIDs = new long[rowObjects.size()]; - int index = 0; - for (SymbolRowObject obj : rowObjects) { - symbolIDs[index++] = obj.getKey(); - } - return new ProgramSymbolActionContext(this, program, symbolIDs, getTable()); + + List symbols = symbolPanel.getSelectedSymbols(); + return new ProgramSymbolActionContext(this, program, symbols, getTable()); } void deleteSymbols() { - List rowObjects = symbolPanel.getSelectedSymbolKeys(); + List rowObjects = symbolPanel.getSelectedSymbols(); symbolKeyModel.delete(rowObjects); } @@ -93,13 +89,17 @@ class SymbolProvider extends ComponentProviderAdapter { } Symbol getCurrentSymbol() { - List rowObjects = symbolPanel.getSelectedSymbolKeys(); + List rowObjects = symbolPanel.getSelectedSymbols(); 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); + } + void setCurrentSymbol(Symbol symbol) { plugin.getReferenceProvider().setCurrentSymbol(symbol); } @@ -126,9 +126,9 @@ class SymbolProvider extends ComponentProviderAdapter { } } - void symbolRemoved(long symbolID) { + void symbolRemoved(Symbol s) { if (isVisible()) { - symbolKeyModel.symbolRemoved(symbolID); + symbolKeyModel.symbolRemoved(s); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java index f0e5609c02..a3f99f8114 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java @@ -131,8 +131,8 @@ public class SymbolReferenceModel extends AddressBasedTableModel { 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); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java index 53d60d6bad..d0ac3f78a8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java @@ -44,7 +44,7 @@ class SymbolRenderer extends GhidraTableCellRenderer { public Component getTableCellRendererComponent(GTableCellRenderingData data) { super.getTableCellRendererComponent(data); - + Object value = data.getValue(); int column = data.getColumnModelIndex(); boolean isSelected = data.isSelected(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java deleted file mode 100644 index abe543a004..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java +++ /dev/null @@ -1,57 +0,0 @@ -/* ### - * 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. - * 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; - -class SymbolRowObject implements Comparable { - - private final long key; - - SymbolRowObject(long key) { - this.key = key; - } - - 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); - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToAddressTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToAddressTableRowMapper.java index 342daf8941..a52a98e0aa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToAddressTableRowMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToAddressTableRowMapper.java @@ -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 { +public class SymbolRowObjectToAddressTableRowMapper + extends ProgramLocationTableRowMapper { @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(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToProgramLocationTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToProgramLocationTableRowMapper.java index a3eb98b151..ec40f67900 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToProgramLocationTableRowMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObjectToProgramLocationTableRowMapper.java @@ -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 { +public class SymbolRowObjectToProgramLocationTableRowMapper + extends ProgramLocationTableRowMapper { @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(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java index cb5ad9b63f..b10af6b47d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java @@ -15,8 +15,7 @@ */ package ghidra.app.plugin.core.symtable; -import java.util.ConcurrentModificationException; -import java.util.List; +import java.util.*; import docking.widgets.table.*; import ghidra.app.cmd.function.DeleteFunctionCmd; @@ -35,30 +34,26 @@ import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.Msg; import ghidra.util.datastruct.Accumulator; -import ghidra.util.datastruct.LongArrayList; import ghidra.util.exception.CancelledException; import ghidra.util.table.AddressBasedTableModel; import ghidra.util.table.column.*; import ghidra.util.table.field.*; import ghidra.util.task.TaskMonitor; -class SymbolTableModel extends AddressBasedTableModel { +class SymbolTableModel extends AddressBasedTableModel { + + private static final Comparator 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; - static final int DATATYPE_COL = 3; + static final int DATA_TYPE_COL = 3; static final int NAMESPACE_COL = 4; static final int SOURCE_COL = 5; static final int REFS_COL = 6; - static final String LABEL_COL_NAME = "Labels"; - static final String LOCATION_COL_NAME = "Location"; - static final String TYPE_COL_NAME = "Type"; - static final String DATATYPE_COL_NAME = "Datatype"; - static final String REFS_COL_NAME = "# Refs"; - static final String NAMESPACE_COL_NAME = "Namespace"; - static final String SOURCE_COL_NAME = "Source"; - private SymbolProvider provider; private PluginTool tool; private SymbolTable symbolTable; @@ -74,14 +69,13 @@ class SymbolTableModel extends AddressBasedTableModel { } @Override - protected TableColumnDescriptor createTableColumnDescriptor() { - TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); - descriptor.addVisibleColumn(new NameTableColumn(), 1, true); - descriptor.addVisibleColumn(new LocationTableColumn()); - descriptor.addVisibleColumn( - DiscoverableTableUtils.adaptColumForModel(this, new SymbolTypeTableColumn())); - descriptor.addVisibleColumn(new DataTypeTableColumn()); + descriptor.addVisibleColumn(new NameTableColumn()); + descriptor.addVisibleColumn(new LocationTableColumn(), 1, true); + descriptor.addVisibleColumn(new SymbolTypeTableColumn()); + descriptor.addHiddenColumn(new DataTypeTableColumn()); descriptor.addVisibleColumn(new NamespaceTableColumn()); descriptor.addVisibleColumn(new SourceTableColumn()); descriptor.addVisibleColumn(new ReferenceCountTableColumn()); @@ -143,7 +137,7 @@ class SymbolTableModel extends AddressBasedTableModel { } @Override - protected void doLoad(Accumulator accumulator, TaskMonitor monitor) + protected void doLoad(Accumulator accumulator, TaskMonitor monitor) throws CancelledException { if (symbolTable == null) { return; @@ -163,7 +157,7 @@ class SymbolTableModel extends AddressBasedTableModel { monitor.checkCanceled(); Symbol s = it.next(); if (filter.accepts(s, getProgram())) { - accumulator.add(new SymbolRowObject(s.getID())); + accumulator.add(s); } } if (filter.acceptsDefaultLabelSymbols()) { @@ -175,7 +169,7 @@ class SymbolTableModel extends AddressBasedTableModel { Address a = addrIt.next(); Symbol s = symbolTable.getPrimarySymbol(a); if (s.isDynamic() && filter.accepts(s, getProgram())) { - accumulator.add(new SymbolRowObject(s.getID())); + accumulator.add(s); } } } @@ -200,32 +194,22 @@ class SymbolTableModel extends AddressBasedTableModel { return; } - SymbolRowObject rowObject = filteredData.get(row); - Symbol symbol = symbolTable.getSymbol(rowObject.getKey()); + Symbol symbol = filteredData.get(row); if (symbol == null) { return; } - switch (columnIndex) { - case LABEL_COL: - try { - String newName = aValue.toString(); - if (!symbol.getName().equals(newName)) { - Command renameCmd = - new RenameLabelCmd(symbol.getAddress(), symbol.getName(), newName, - symbol.getParentNamespace(), SourceType.USER_DEFINED); + if (columnIndex == LABEL_COL) { + String newName = aValue.toString(); + if (!symbol.getName().equals(newName)) { + Command renameCmd = new RenameLabelCmd(symbol.getAddress(), symbol.getName(), + newName, symbol.getParentNamespace(), SourceType.USER_DEFINED); - if (!tool.execute(renameCmd, getProgram())) { - Msg.showError(getClass(), provider.getComponent(), - "Error Renaming Symbol", renameCmd.getStatusMsg()); - } - } + if (!tool.execute(renameCmd, getProgram())) { + Msg.showError(getClass(), provider.getComponent(), "Error Renaming Symbol", + renameCmd.getStatusMsg()); } - catch (ConcurrentModificationException exc) { - Msg.showError(getClass(), provider.getComponent(), "Invalid Symbol", - "Symbol no longer valid."); - } - break; + } } } @@ -262,40 +246,37 @@ class SymbolTableModel extends AddressBasedTableModel { void symbolAdded(Symbol s) { if (filter.accepts(s, getProgram())) { - addObject(new SymbolRowObject(s.getID())); + addObject(s); lastSymbol = s; } } - void symbolRemoved(long symbolID) { - if (lastSymbol != null && lastSymbol.getID() == symbolID) { + void symbolRemoved(Symbol s) { + if (lastSymbol != null && lastSymbol.getID() == s.getID()) { lastSymbol = null; } - removeObject(new SymbolRowObject(symbolID)); + removeObject(s); } void symbolChanged(Symbol s) { - SymbolRowObject symbolRowObject = new SymbolRowObject(s.getID()); + Symbol Symbol = s; if (filter.accepts(s, getProgram())) { - updateObject(symbolRowObject); + updateObject(Symbol); } else { - removeObject(symbolRowObject); + removeObject(Symbol); } } - void delete(List rowObjects) { + void delete(List rowObjects) { if (rowObjects == null || rowObjects.size() == 0) { return; } + tool.setStatusInfo(""); - LongArrayList deleteList = new LongArrayList(); + List 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) { @@ -303,7 +284,8 @@ class SymbolTableModel extends AddressBasedTableModel { continue;//can't delete dynamic symbols... } } - deleteList.add(rowObjects.get(i).getKey()); + + deleteList.add(symbol); String label = symbol.getName(); if (symbol.getSymbolType() == SymbolType.FUNCTION) { Function function = (Function) symbol.getObject(); @@ -323,9 +305,10 @@ class SymbolTableModel extends AddressBasedTableModel { 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(); } @@ -339,56 +322,16 @@ class SymbolTableModel extends AddressBasedTableModel { return filter; } - @Override - public Class getSortedColumnClass(int columnIndex) { - if (columnIndex == LOCATION_COL) { - return Address.class; - } - return super.getSortedColumnClass(columnIndex); - } - - public static int getPreferredWidth(int columnIndex) { - switch (columnIndex) { - case LABEL_COL: - return 140; - case LOCATION_COL: - return 40; - case DATATYPE_COL: - case TYPE_COL: - case SOURCE_COL: - return 30; - case NAMESPACE_COL: - return 80; - case REFS_COL: - return 20; - } - return 40; - } - @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(); } - Symbol getSymbolForRowObject(SymbolRowObject storageObject) { - if (symbolTable == null) { - return null; - } - - long key = storageObject.getKey(); - Symbol localSymbol = lastSymbol; - if (localSymbol == null || localSymbol.getID() != key) { - localSymbol = lastSymbol = symbolTable.getSymbol(key); - } - return localSymbol; - } - - AddressBasedLocation getSymbolLocation(SymbolRowObject rowObject) { - Symbol s = getSymbolForRowObject(rowObject); + private AddressBasedLocation getSymbolLocation(Symbol s) { if (s == null) { return new AddressBasedLocation(); } @@ -396,17 +339,36 @@ class SymbolTableModel extends AddressBasedTableModel { 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 createSortComparator(int columnIndex) { + DynamicTableColumn 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 { + private class NameTableColumn extends AbstractProgramBasedDynamicTableColumn { @Override public String getColumnName() { @@ -414,14 +376,18 @@ class SymbolTableModel extends AddressBasedTableModel { } @Override - public Symbol getValue(SymbolRowObject rowObject, Settings settings, Program p, + public Symbol getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - return getSymbolForRowObject(rowObject); + + if (!symbol.checkIsValid()) { + return null; + } + return symbol; } } private class PinnedTableColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { private PinnedRenderer renderer = new PinnedRenderer(); @@ -431,10 +397,10 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + + if (!symbol.checkIsValid()) { return null; } return symbol.isPinned(); @@ -452,7 +418,7 @@ class SymbolTableModel extends AddressBasedTableModel { } private class LocationTableColumn - extends AbstractProgramLocationTableColumn { + extends AbstractProgramLocationTableColumn { @Override public String getColumnName() { @@ -460,22 +426,44 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + public ProgramLocation getProgramLocation(Symbol symbol, Settings settings, Program p, + ServiceProvider svcProvider) { + + if (!symbol.checkIsValid()) { return null; } return symbol.getProgramLocation(); } } + private class SymbolTypeTableColumn + extends AbstractProgramBasedDynamicTableColumn { + + @Override + public String getColumnName() { + return "Type"; + } + + @Override + public String getValue(Symbol symbol, Settings settings, Program p, + ServiceProvider svcProvider) throws IllegalArgumentException { + + 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(symbol); + } + } + private class VariableSymbolLocation extends AddressBasedLocation { VariableSymbolLocation(Variable variable) { @@ -484,7 +472,7 @@ class SymbolTableModel extends AddressBasedTableModel { } private class DataTypeTableColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { @Override public String getColumnName() { @@ -492,11 +480,10 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + if (!symbol.checkIsValid()) { return null; } @@ -522,7 +509,7 @@ class SymbolTableModel extends AddressBasedTableModel { } private class NamespaceTableColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { @Override public String getColumnName() { @@ -530,19 +517,18 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + + if (!symbol.checkIsValid()) { return null; } - return symbol.getParentNamespace().getName(true); } } private class SourceTableColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { private GColumnRenderer renderer = new AbstractGColumnRenderer() { @Override @@ -570,9 +556,9 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); + if (symbol == null) { return null; } @@ -582,7 +568,9 @@ class SymbolTableModel extends AddressBasedTableModel { } private class ReferenceCountTableColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { + + private ReferenceCountRenderer renderer = new ReferenceCountRenderer(); @Override public String getColumnName() { @@ -590,19 +578,31 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + if (!symbol.checkIsValid()) { return null; } - return Integer.valueOf(symbol.getReferenceCount()); } + + @Override + public GColumnRenderer getColumnRenderer() { + return renderer; + } + + // this renderer disables the default text filtering; this column is only filterable + // via the column constraint filtering + private class ReferenceCountRenderer extends GTableCellRenderer + implements AbstractWrapperTypeColumnRenderer { + // body is handled by parents + } } private class OffcutReferenceCountTableColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { + + private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer(); @Override public String getColumnName() { @@ -610,11 +610,9 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + if (!symbol.checkIsValid()) { return null; } @@ -637,10 +635,21 @@ class SymbolTableModel extends AddressBasedTableModel { } return Integer.valueOf(count); } + + @Override + public GColumnRenderer getColumnRenderer() { + return renderer; + } + + // this renderer disables the default text filtering; this column is only filterable + // via the column constraint filtering + private class OffcutReferenceCountRenderer extends GTableCellRenderer + implements AbstractWrapperTypeColumnRenderer { + // body is handled by parents + } } - private class UserTableColumn - extends AbstractProgramBasedDynamicTableColumn { + private class UserTableColumn extends AbstractProgramBasedDynamicTableColumn { @Override public String getColumnName() { @@ -653,11 +662,10 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - if (symbol == null) { + if (!symbol.checkIsValid()) { return null; } @@ -678,7 +686,7 @@ class SymbolTableModel extends AddressBasedTableModel { } private class OriginalNameColumn - extends AbstractProgramBasedDynamicTableColumn { + extends AbstractProgramBasedDynamicTableColumn { @Override public String getColumnName() { @@ -691,13 +699,17 @@ class SymbolTableModel extends AddressBasedTableModel { } @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 = getSymbolForRowObject(rowObject); - 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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java index fbfa385b4b..95d35fc0d6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java @@ -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; @@ -190,7 +189,10 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { if (!symProvider.isVisible()) { return; } - if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { + if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) || + ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_ADDED) || + ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) { + symProvider.reload(); refProvider.reload(); return; @@ -207,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); @@ -221,9 +224,9 @@ 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.getID()); + symProvider.symbolRemoved(primaryAtAdd); } symbol = (Symbol) rec.getNewValue(); symProvider.symbolAdded(symbol); @@ -233,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); } @@ -271,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); @@ -281,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); @@ -295,19 +300,12 @@ 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); } break; - - case ChangeManager.DOCR_MEMORY_BLOCK_ADDED: - case ChangeManager.DOCR_MEMORY_BLOCK_REMOVED: - symProvider.reload(); - refProvider.reload(); - break; - } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index 329cc6d3cb..143c2d8b1a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -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 void tx(Program p, ExceptionalCallback 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 void tx(Program p, ExceptionalCallback 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 void modifyProgram(Program program, - ExceptionalConsumer 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 void modifyProgram(Program p, ExceptionalCallback 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 createInProgram(Program program, + public R modifyProgram(Program program, ExceptionalFunction f) { assertNotNull("Program cannot be null", program); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionBodySizeTableColumn.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionBodySizeTableColumn.java index 1a1903f36b..de07467acd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionBodySizeTableColumn.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionBodySizeTableColumn.java @@ -15,14 +15,19 @@ */ package ghidra.util.table.field; +import docking.widgets.table.GTableCellRenderer; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; +import ghidra.util.table.column.AbstractWrapperTypeColumnRenderer; +import ghidra.util.table.column.GColumnRenderer; public class FunctionBodySizeTableColumn extends ProgramBasedDynamicTableColumnExtensionPoint { + private FunctionBodySizeRenderer renderer = new FunctionBodySizeRenderer(); + @Override public String getColumnName() { return "Function Size"; @@ -34,4 +39,15 @@ public class FunctionBodySizeTableColumn return (int) rowObject.getBody().getNumAddresses(); } + @Override + public GColumnRenderer getColumnRenderer() { + return renderer; + } + + // this renderer disables the default text filtering; this column is only filterable + // via the column constraint filtering + private class FunctionBodySizeRenderer extends GTableCellRenderer + implements AbstractWrapperTypeColumnRenderer { + // body is handled by parents + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java index e79fd67133..ec96d9ea3d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java @@ -18,7 +18,8 @@ package ghidra.app.plugin.core.symtable; import static org.junit.Assert.*; import java.awt.*; -import java.awt.event.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -26,7 +27,7 @@ import java.util.function.BiConsumer; import javax.swing.*; import javax.swing.event.ChangeEvent; -import javax.swing.table.*; +import javax.swing.table.TableModel; import org.jdom.Element; import org.junit.*; @@ -39,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; @@ -66,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 filterPanel; + private SymbolProvider provider; + private DockingActionIf viewSymAction; private DockingActionIf viewRefAction; private DockingActionIf deleteAction; @@ -75,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 filterPanel; - private SymbolProvider provider; @Before public void setUp() throws Exception { @@ -91,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); @@ -118,52 +120,61 @@ 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 public void testSortingLabelColumn() throws Exception { openProgram("sample"); - Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LABEL_COL); - - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - TableColumn column = - symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LABEL_COL); - GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer(); - assertTrue(renderer.isSortedAscending()); + sortAscending(SymbolTableModel.LABEL_COL); TableModel model = symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - Symbol sym1 = (Symbol) model.getValueAt(i + 0, SymbolTableModel.LABEL_COL); - Symbol sym2 = (Symbol) model.getValueAt(i + 1, SymbolTableModel.LABEL_COL); + Symbol sym1 = getSymbol(i); + Symbol sym2 = getSymbol(i + 1); int compare = sym1.getName().compareToIgnoreCase(sym2.getName()); assertTrue("row " + i + " not sorted correctly", (compare < 0 || compare == 0)); } - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - assertTrue(!renderer.isSortedAscending()); + sortDescending(SymbolTableModel.LABEL_COL); model = symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - Symbol sym1 = (Symbol) model.getValueAt(i + 0, SymbolTableModel.LABEL_COL); - Symbol sym2 = (Symbol) model.getValueAt(i + 1, SymbolTableModel.LABEL_COL); + Symbol sym1 = getSymbol(i); + Symbol sym2 = getSymbol(i + 1); int compare = sym1.getName().compareToIgnoreCase(sym2.getName()); assertTrue("row " + i + " not sorted correctly", (compare > 0 || compare == 0)); } } + private void sortAscending(int column) { + runSwing(() -> symbolModel.setTableSortState( + TableSortState.createDefaultSortState(column, true))); + waitForTableModel(symbolModel); + + waitForCondition(() -> { + TableSortState sort = runSwing(() -> symbolModel.getTableSortState()); + return sort.getColumnSortState(column).isAscending(); + }); + } + + private void sortDescending(int column) { + runSwing(() -> symbolModel.setTableSortState( + TableSortState.createDefaultSortState(column, false))); + waitForTableModel(symbolModel); + + waitForCondition(() -> { + TableSortState sort = runSwing(() -> symbolModel.getTableSortState()); + return !sort.getColumnSortState(column).isAscending(); + }); + } + @Test public void testColumnDiscovery() throws Exception { // @@ -205,35 +216,21 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { public void testSortingAddressColumn() throws Exception { openProgram("sample"); - Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LOCATION_COL); - - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - TableColumn column = - symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LOCATION_COL); - GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer(); - assertTrue(renderer.isSortedAscending()); + sortAscending(SymbolTableModel.LOCATION_COL); SymbolTableModel model = (SymbolTableModel) symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - AddressBasedLocation refs1 = - (AddressBasedLocation) model.getValueAt(i + 0, SymbolTableModel.LOCATION_COL); - AddressBasedLocation refs2 = - (AddressBasedLocation) model.getValueAt(i + 1, SymbolTableModel.LOCATION_COL); - assertTrue(refs1.compareTo(refs2) <= 0); + AddressBasedLocation loc1 = getLocation(i); + AddressBasedLocation loc2 = getLocation(i + 0); + assertTrue(loc1.compareTo(loc2) <= 0); } - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - assertTrue(!renderer.isSortedAscending()); + sortDescending(SymbolTableModel.LOCATION_COL); for (int i = 0; i < model.getRowCount() - 1; ++i) { - AddressBasedLocation refs1 = - (AddressBasedLocation) model.getValueAt(i + 0, SymbolTableModel.LOCATION_COL); - AddressBasedLocation refs2 = - (AddressBasedLocation) model.getValueAt(i + 1, SymbolTableModel.LOCATION_COL); - assertTrue(refs1.compareTo(refs2) >= 0); + AddressBasedLocation loc1 = getLocation(i); + AddressBasedLocation loc2 = getLocation(i + 0); + assertTrue(loc1.compareTo(loc2) >= 0); } } @@ -241,26 +238,20 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { public void testSortingReferenceColumn() throws Exception { openProgram("sample"); - sortOnColumn(SymbolTableModel.REFS_COL); - - TableColumn column = - symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.REFS_COL); - GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer(); - assertTrue(renderer.isSortedAscending()); + sortAscending(SymbolTableModel.REFS_COL); TableModel model = symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - Integer refs1 = (Integer) model.getValueAt(i + 0, SymbolTableModel.REFS_COL); - Integer refs2 = (Integer) model.getValueAt(i + 1, SymbolTableModel.REFS_COL); + Integer refs1 = getRefCount(i); + Integer refs2 = getRefCount(i + 1); assertTrue(refs1.compareTo(refs2) <= 0); } - sortOnColumn(SymbolTableModel.REFS_COL); - assertTrue(!renderer.isSortedAscending()); + sortDescending(SymbolTableModel.REFS_COL); for (int i = 0; i < model.getRowCount() - 1; ++i) { - Integer refs1 = (Integer) model.getValueAt(i + 0, SymbolTableModel.REFS_COL); - Integer refs2 = (Integer) model.getValueAt(i + 1, SymbolTableModel.REFS_COL); + Integer refs1 = getRefCount(i); + Integer refs2 = getRefCount(i + 1); assertTrue(refs1.compareTo(refs2) >= 0); } } @@ -359,7 +350,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { waitForNotBusy(symbolTable); - int row = findRow("ghidra", "Global"); + String symbolName = "ghidra"; + int row = findRow(symbolName); doubleClick(symbolTable, row, SymbolTableModel.LABEL_COL); waitForSwing(); @@ -368,6 +360,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { Component editor = symbolTable.getEditorComponent(); assertNotNull(editor); JTextField textField = (JTextField) editor; + String currentText = getText(textField); + assertEquals(symbolName, currentText); + triggerActionKey(textField, 0, KeyEvent.VK_END); myTypeText(editor, ".Is.Cool"); runSwing(() -> symbolTable.editingStopped(new ChangeEvent(symbolTable))); @@ -376,7 +371,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(!symbolTable.isEditing()); - Symbol s = (Symbol) symbolTable.getValueAt(row, SymbolTableModel.LABEL_COL); + Symbol s = getSymbol(row); assertEquals("ghidra.Is.Cool", s.getName()); } @@ -384,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()); } @@ -437,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. @@ -489,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); @@ -503,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 @@ -526,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()); @@ -538,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, @@ -560,77 +548,78 @@ 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 = (Symbol) symbolTable.getValueAt(selectedRow, 0); - assertTrue(!symbol.isPinned()); + Symbol symbol = getSymbol(selectedRow); + 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 = (Symbol) symbolTable.getValueAt(selectedRow, 0); + Symbol symbol = getSymbol(selectedRow); assertTrue(symbol.isPinned()); } performAction(clearPinnedAction, actionContext, true); waitForSwing(); for (int selectedRow : selectedRows) { - Symbol symbol = (Symbol) symbolTable.getValueAt(selectedRow, 0); - assertTrue(!symbol.isPinned()); + Symbol symbol = getSymbol(selectedRow); + 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(); for (int selectedRow : selectedRows) { - Symbol symbol = (Symbol) symbolTable.getValueAt(selectedRow, 0); - assertTrue(!symbol.isPinned()); + Symbol symbol = getSymbol(selectedRow); + assertFalse(symbol.isPinned()); } - assertTrue(!setPinnedAction.isEnabledForContext(actionContext)); - assertTrue(!clearPinnedAction.isEnabledForContext(actionContext)); + assertFalse(setPinnedAction.isEnabledForContext(actionContext)); + assertFalse(clearPinnedAction.isEnabledForContext(actionContext)); } @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); - waitForNotBusy(symbolTable); - assertEquals(rowCount + 1, symbolTable.getRowCount()); - assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) >= 0); - sym = - st.createLabel(sample.getNewAddress(0x01007100), "Athena", SourceType.USER_DEFINED); - waitForNotBusy(symbolTable); - assertEquals(rowCount + 2, symbolTable.getRowCount()); - assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) >= 0); - } - finally { - prog.endTransaction(id, true); - } + 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(sym) >= 0); + + sym = modifyProgram(program, p -> { + return st.createLabel(sample.getNewAddress(0x01007100), "Athena", + SourceType.USER_DEFINED); + }); + waitForNotBusy(symbolTable); + assertEquals(rowCount + 2, symbolTable.getRowCount()); + assertTrue(symbolModel.getRowIndex(sym) >= 0); } @Test @@ -651,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); - waitForNotBusy(symbolTable); - assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) >= 0); - } - finally { - prog.endTransaction(id, true); - } - + 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); - assertEquals(rowCount + 1, symbolModel.getRowCount());// make sure we added one while the filter is on + assertTrue(symbolModel.getRowIndex(sym) >= 0); + + // make sure we added one while the filter is on + assertEquals(rowCount + 1, symbolModel.getRowCount()); } @Test @@ -693,65 +677,55 @@ 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(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) == -1); + 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.getID())); - Integer refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL); + 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.getID())); + row = symbolModel.getRowIndex(s); - refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL); + refCount = getRefCount(row); assertNotNull(refCount); assertEquals(4, refCount.intValue()); } @@ -759,71 +733,68 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testUpdateOnReferencesRemoved() throws Exception { openProgram("sample"); - Address sample = prog.getMinAddress(); + Address sample = program.getMinAddress(); - Symbol s = getUniqueSymbol(prog, "doStuff"); - - int row = symbolModel.getRowIndex(new SymbolRowObject(s.getID())); - - Integer refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL); + 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 = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL); + refCount = getRefCount(row); assertNotNull(refCount); 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); - waitForBusyTool(tool); - } - finally { - prog.endTransaction(id, true); - } + int startRowCount = symbolTable.getRowCount(); + + ClearCmd cmd = new ClearCmd(program.getMemory(), new ClearOptions()); + applyCmd(program, cmd); + waitForBusyTool(tool); 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 @@ -831,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"); @@ -853,23 +823,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { /************** LABEL **********************/ - Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LABEL_COL); - - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - TableColumn column = - symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LABEL_COL); - GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer(); - assertTrue(renderer.isSortedAscending()); + sortAscending(SymbolTableModel.LABEL_COL); TableModel model = symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - Symbol sym1 = (Symbol) model.getValueAt(i + 0, SymbolTableModel.LABEL_COL); - Symbol sym2 = (Symbol) model.getValueAt(i + 1, SymbolTableModel.LABEL_COL); + Symbol sym1 = getSymbol(i); + Symbol sym2 = getSymbol(i + 1); int compare = sym1.getName().compareToIgnoreCase(sym2.getName()); assertTrue("Symbol \"" + sym1 + "\" is not sorted as less than symbol \"" + sym2 + "\"", compare <= 0); @@ -877,21 +836,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { /************** ADDRESS **********************/ - rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LOCATION_COL); - - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - column = symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LOCATION_COL); - renderer = (GTableHeaderRenderer) column.getHeaderRenderer(); - assertTrue(renderer.isSortedAscending()); + sortAscending(SymbolTableModel.LOCATION_COL); model = symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - AddressBasedLocation loc1 = - (AddressBasedLocation) model.getValueAt(i, SymbolTableModel.LOCATION_COL); - AddressBasedLocation loc2 = - (AddressBasedLocation) model.getValueAt(i + 1, SymbolTableModel.LOCATION_COL); + AddressBasedLocation loc1 = getLocation(i); + AddressBasedLocation loc2 = getLocation(i + 1); int compare = SystemUtilities.compareTo(loc1, loc2); assertTrue( "Location1 \"" + loc1 + "\"is not sorted as less than location2 \"" + loc2 + "\"", @@ -900,18 +850,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { /************** REFERENCES **********************/ - rect = symbolTableHeader.getHeaderRect(SymbolTableModel.REFS_COL); - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - - column = symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.REFS_COL); - renderer = (GTableHeaderRenderer) column.getHeaderRenderer(); - assertTrue(renderer.isSortedAscending()); + sortAscending(SymbolTableModel.REFS_COL); model = symbolTable.getModel(); for (int i = 0; i < model.getRowCount() - 1; ++i) { - Integer refs1 = (Integer) model.getValueAt(i + 0, SymbolTableModel.REFS_COL); - Integer refs2 = (Integer) model.getValueAt(i + 1, SymbolTableModel.REFS_COL); + Integer refs1 = getRefCount(i); + Integer refs2 = getRefCount(i + 1); assertTrue( "The number of references (\"" + refs1 + "\") for row did not " + "compare as less than the number for the following row (\"" + refs2 + "\")", @@ -1035,7 +979,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { deleteText(textField); // sort on a different column to trigger the other kind of filtering - sortOnColumn(SymbolTableModel.REFS_COL); + sortAscending(SymbolTableModel.REFS_COL); text = "_"; myTypeText(textField, text); @@ -1106,7 +1050,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { deleteText(textField); // sort on a different column to trigger the other kind of filtering - sortOnColumn(SymbolTableModel.LOCATION_COL); + sortAscending(SymbolTableModel.LOCATION_COL); text = "_"; myTypeText(textField, text); @@ -1134,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")); @@ -1172,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 popupItems, String string) { for (JMenuItem item : popupItems) { String text = item.getText(); @@ -1206,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() { @@ -1256,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); @@ -1270,19 +1248,29 @@ 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) { + return symbolModel.getRowObject(row); + } + + private Integer getRefCount(int row) { + Integer count = + runSwing(() -> (Integer) symbolModel.getValueAt(row, SymbolTableModel.REFS_COL)); + return count; + } + + private AddressBasedLocation getLocation(int row) { + AddressBasedLocation location = + runSwing(() -> (AddressBasedLocation) symbolModel.getValueAt(row, + SymbolTableModel.LOCATION_COL)); + return location; } private void assertReferencesAddressColumnValue(int row, long value) { @@ -1292,12 +1280,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { addr.getOffset()); } - private void sortOnColumn(int column) throws Exception { - Rectangle rect = symbolTableHeader.getHeaderRect(column); - clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); - waitForNotBusy(symbolTable); - } - private void deleteText(JTextField field) throws Exception { deleteText(field, false, false); } @@ -1328,14 +1310,10 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { } } - private void setFilterOptions(TextFilterStrategy filterStrategy, boolean caseSensitive) { + private void setFilterOptions(TextFilterStrategy filterStrategy, boolean caseSensitive) + throws Exception { filterPanel.setFilterOptions(new FilterOptions(filterStrategy, true, caseSensitive, false)); - waitForTable(); - - } - - private void waitForTable() { - waitForSwing(); + waitForNotBusy(symbolTable); } private JTextField getFilterTextField() { @@ -1376,42 +1354,18 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { private int getRowForSymbol(Symbol symbol) { for (int i = 0; i < symbolTable.getRowCount(); i++) { - Object name = symbolTable.getValueAt(i, 0); - if (name.toString().equals(symbol.getName())) { - Object namespace = symbolTable.getValueAt(i, 4); - if (namespace.toString().equals(symbol.getParentNamespace().getName())) { - return i; - } + Symbol rowSymbol = getSymbol(i); + if (rowSymbol.equals(symbol)) { +// if (rowSymbol.getParentNamespace().equals(symbol.getParentNamespace())) { +// return i; +// } + return i; } } Assert.fail("Didn't find symbol in symbol table: " + symbol.getName()); 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); @@ -1421,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 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."); @@ -1433,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 { @@ -1478,7 +1432,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { "There are no filtered matches as expected from filter string: " + filterString, rowCount > 0); for (int i = 0; i < rowCount; i++) { - Symbol symbol = (Symbol) symbolModel.getValueAt(i, SymbolTableModel.LABEL_COL); + Symbol symbol = getSymbol(i); assertTrue( "Found an entry in the symbol table model that " + "does not match the given " + "filter: " + filterString + " and symbol: " + symbol.getName(), @@ -1495,7 +1449,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { String filterText = string; for (int i = 0; i < rowCount; i++) { - Symbol symbol = (Symbol) symbolModel.getValueAt(i, SymbolTableModel.LABEL_COL); + Symbol symbol = getSymbol(i); assertTrue( "Found an entry in the symbol table model that does not match the given " + "filter: " + string + " and symbol: " + symbol.getName(), @@ -1504,8 +1458,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { } private void waitForNotBusy(GTable table) throws Exception { - prog.flushEvents(); - + waitForProgram(program); ThreadedTableModel model = (ThreadedTableModel) table.getModel(); waitForTableModel(model); } @@ -1513,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); @@ -1566,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 @@ -1590,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(); } @@ -1631,7 +1585,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { waitForNotBusy(symbolTable); - symbolTableHeader = symbolTable.getTableHeader(); + sortAscending(SymbolTableModel.LABEL_COL); } private void showReferencesTable() { @@ -1644,11 +1598,10 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { } @SuppressWarnings("unchecked") - private GhidraTableFilterPanel getFilterPanel() { + private GhidraTableFilterPanel getFilterPanel() { Object symProvider = getInstanceField("symProvider", plugin); Object panel = getInstanceField("symbolPanel", symProvider); - return (GhidraTableFilterPanel) getInstanceField("tableFilterPanel", - panel); + return (GhidraTableFilterPanel) getInstanceField("tableFilterPanel", panel); } private void singleClick(final JTable table, final int row, final int col) throws Exception { @@ -1676,6 +1629,10 @@ 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++) { @@ -1685,7 +1642,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { return i; } } - Assert.fail("Symbol cell not found: " + namespace + "::" + symbolName); + return -1; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java index 13f0a82ad7..88c901c0ca 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java @@ -21,7 +21,7 @@ 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.RowBasedColumnComparator; import ghidra.util.Swing; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; @@ -92,8 +92,8 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * Returns the index of the given row object in this model; -1 if the model does not contain - * the given object. + * Returns the index of the given row object in this model; a negative value if the model + * does not contain the given object. * *

Warning: if the this model has no sort applied, then performance will be O(n). If * sorted, then performance is O(log n). You can call {@link #isSorted()} to know when @@ -132,6 +132,9 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel @Override public int getPrimarySortColumnIndex() { + if (sortState.isUnsorted()) { + return -1; + } return sortState.iterator().next().getColumnModelIndex(); } @@ -327,8 +330,8 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel * @return the comparator */ protected Comparator createSortComparator(int columnIndex) { - return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), - new StringBasedBackupRowToColumnComparator(columnIndex)); + return new RowBasedColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), + new StringBasedBackupRowToColumnComparator()); } private Comparator createLastResortComparator(ComparatorLink parentChain) { @@ -467,22 +470,16 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } } - private class StringBasedBackupRowToColumnComparator implements Comparator { - - private int sortColumn; - - StringBasedBackupRowToColumnComparator(int sortColumn) { - this.sortColumn = sortColumn; - } + private class StringBasedBackupRowToColumnComparator implements Comparator { @Override - public int compare(T t1, T t2) { - if (t1 == t2) { + public int compare(Object c1, Object c2) { + if (c1 == c2) { 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); @@ -491,11 +488,10 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel 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(); } } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AddRemoveListItem.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AddRemoveListItem.java index 432c8d7bdb..a33d660047 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AddRemoveListItem.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AddRemoveListItem.java @@ -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 { public boolean isRemove() { return isRemove; } + public boolean isChange() { return isAdd && isRemove; } @@ -41,4 +41,16 @@ public class AddRemoveListItem { public T getValue() { return value; } + + @Override + public String toString() { + + //@formatter:off + return "{\n" + + "\tvalue: " + value +",\n" + + "\tisAdd: " + isAdd +",\n" + + "\tisRemove: " + isRemove +"\n" + + "}"; + //@formatter:on + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java index e56d9e9a56..6466c06d11 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java @@ -57,6 +57,13 @@ public class DefaultRowFilterTransformer implements RowFilterTransfo } } + if (columnUsesConstraintFilteringOnly(column)) { + // This allows columns to be ignored for default text filtering while still being + // filterable through the column constraints API + return null; + } + + // note: this call can be slow when columns dynamically calculate values from the database Object value = model.getColumnValueForRow(rowObject, column); if (value == null) { return null; @@ -96,7 +103,22 @@ public class DefaultRowFilterTransformer implements RowFilterTransfo return value.toString(); } - @SuppressWarnings("unchecked") + private boolean columnUsesConstraintFilteringOnly(int column) { + if (!(model instanceof DynamicColumnTableModel)) { + return false; + } + + DynamicColumnTableModel columnBasedModel = + (DynamicColumnTableModel) model; + GColumnRenderer renderer = getColumnRenderer(columnBasedModel, column); + if (renderer == null) { + return false; + } + + ColumnConstraintFilterMode mode = renderer.getColumnConstraintFilterMode(); + return mode == ColumnConstraintFilterMode.USE_COLUMN_CONSTRAINTS_ONLY; + } + private String getRenderedColumnValue(Object columnValue, int columnIndex) { if (!(model instanceof DynamicColumnTableModel)) { @@ -110,11 +132,6 @@ public class DefaultRowFilterTransformer implements RowFilterTransfo return null; } - ColumnConstraintFilterMode mode = renderer.getColumnConstraintFilterMode(); - if (mode == ColumnConstraintFilterMode.USE_COLUMN_CONSTRAINTS_ONLY) { - return null; // this renderer does not support text - } - Settings settings = columnBasedModel.getColumnSettings(columnIndex); String s = renderer.getFilterString(columnValue, settings); return s; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java index 556a16011e..589d190182 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java @@ -178,11 +178,11 @@ public abstract class GDynamicColumnTableModel Comparator columnComparator = createSortComparatorForColumn(columnIndex); if (columnComparator != null) { // the given column has its own comparator; wrap and us that - return new RowToColumnComparator<>(this, columnIndex, columnComparator); + return new RowBasedColumnComparator<>(this, columnIndex, columnComparator); } - return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), - new ColumnRenderedValueBackupRowComparator<>(this, columnIndex)); + return new RowBasedColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), + new ColumnRenderedValueBackupComparator<>(this, columnIndex)); } /** @@ -441,7 +441,6 @@ public abstract class GDynamicColumnTableModel } return column.getValue(t, columnSettings.get(column), dataSource, serviceProvider); - } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableColumnModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableColumnModel.java index 23160b9dfb..2e000bb416 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableColumnModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableColumnModel.java @@ -33,7 +33,7 @@ import ghidra.util.datastruct.WeakSet; public class GTableColumnModel implements TableColumnModel, PropertyChangeListener, ListSelectionListener { - private List visibleList = new ArrayList<>(); + private VisibleColumns visibleColumns = new VisibleColumns(); private List completeList = new ArrayList<>(); private int totalColumnWidth; private int columnMargin; @@ -67,18 +67,8 @@ public class GTableColumnModel void removeAllColumns() { - TableColumn[] asArray = visibleList.toArray(new TableColumn[visibleList.size()]); - for (int i = 0; i < asArray.length; i++) { - TableColumn column = asArray[i]; - visibleList.remove(column); - fireColumnRemoved(new TableColumnModelEvent(this, i, i)); - } - - /* - TODO replace the above snippet with this code, after the upcoming release - fireColumnRemoved(new TableColumnModelEvent(this, 0, visibleList.size() - 1)); - visibleList.clear(); - */ + fireColumnRemoved(new TableColumnModelEvent(this, 0, visibleColumns.size() - 1)); + visibleColumns.clear(); // no need to fire the removed event for items in the complete list, as the clients // only know about the visible columns @@ -90,7 +80,7 @@ public class GTableColumnModel void dispose() { listeners.clear(); - visibleList.clear(); + visibleColumns.clear(); completeList.clear(); columnModelState.dispose(); } @@ -101,7 +91,7 @@ public class GTableColumnModel * @return true if the given column is visible. */ public boolean isVisible(TableColumn column) { - return visibleList.contains(column); + return visibleColumns.contains(column); } /** @@ -119,7 +109,7 @@ public class GTableColumnModel } public void setVisible(TableColumn column, boolean visible) { - boolean isVisible = visibleList.contains(column); + boolean isVisible = visibleColumns.contains(column); if (visible == isVisible) { return; @@ -127,12 +117,12 @@ public class GTableColumnModel if (visible) { int insertIndex = findVisibleInsertionIndex(column); - visibleList.add(insertIndex, column); + visibleColumns.add(insertIndex, column); fireColumnAdded(new TableColumnModelEvent(this, insertIndex, insertIndex)); } else { - int columnIndex = visibleList.indexOf(column); - visibleList.remove(columnIndex); + int columnIndex = visibleColumns.indexOf(column); + visibleColumns.remove(columnIndex); // Adjust for the selection if (selectionModel != null) { selectionModel.removeIndexInterval(columnIndex, columnIndex); @@ -146,12 +136,12 @@ public class GTableColumnModel } private int findVisibleInsertionIndex(TableColumn column) { - int completeIndex = completeList.indexOf(column); + int completeIndex = visibleColumns.indexOf(column); - int size = visibleList.size(); + int size = visibleColumns.size(); for (int i = completeIndex + 1; i < size; i++) { TableColumn nextColumn = completeList.get(i); - int visibleIndex = visibleList.indexOf(nextColumn); + int visibleIndex = visibleColumns.indexOf(nextColumn); if (visibleIndex != -1) { return visibleIndex; } @@ -169,7 +159,7 @@ public class GTableColumnModel removeColumnWithModelIndex(aColumn.getModelIndex()); // dedup completeList.add(aColumn); - visibleList.add(aColumn); + visibleColumns.add(aColumn); aColumn.addPropertyChangeListener(this); @@ -205,7 +195,7 @@ public class GTableColumnModel } completeList.remove(tableColumn); - visibleList.remove(tableColumn); + visibleColumns.remove(tableColumn); tableColumn.removePropertyChangeListener(this); } @@ -216,15 +206,15 @@ public class GTableColumnModel @Override public TableColumn getColumn(int columnIndex) { - if ((columnIndex < 0) || (columnIndex >= visibleList.size())) { + if ((columnIndex < 0) || (columnIndex >= visibleColumns.size())) { return null; } - return visibleList.get(columnIndex); + return visibleColumns.get(columnIndex); } @Override public int getColumnCount() { - return visibleList.size(); + return visibleColumns.size(); } @Override @@ -232,8 +222,8 @@ public class GTableColumnModel if (columnIdentifier == null) { throw new IllegalArgumentException("Identifier is null"); } - for (int i = 0; i < visibleList.size(); i++) { - TableColumn tableColumn = visibleList.get(i); + for (int i = 0; i < visibleColumns.size(); i++) { + TableColumn tableColumn = visibleColumns.get(i); if (columnIdentifier.equals(tableColumn.getIdentifier())) { return i; } @@ -271,7 +261,7 @@ public class GTableColumnModel @Override public Enumeration getColumns() { - return Collections.enumeration(visibleList); + return visibleColumns.toEnumeration(); } /** @@ -355,8 +345,8 @@ public class GTableColumnModel } // update the visible list - TableColumn movedColumn = visibleList.remove(columnIndex); - visibleList.add(newIndex, movedColumn); + TableColumn movedColumn = visibleColumns.remove(columnIndex); + visibleColumns.add(newIndex, movedColumn); // update the complete list completeList.remove(movedColumn); @@ -364,7 +354,7 @@ public class GTableColumnModel // get the item at the index after the new index (since we are moving up, we know // that there are columns below the new index) - TableColumn column = visibleList.get(newIndex + 1); + TableColumn column = visibleColumns.get(newIndex + 1); // find this column in the complete list and then place the moved column before that // position in the complete list @@ -375,7 +365,7 @@ public class GTableColumnModel // get the item at the index before the new index (since we are moving down, we know // that there are columns above the new index) - TableColumn column = visibleList.get(newIndex - 1); + TableColumn column = visibleColumns.get(newIndex - 1); // find this column in the complete list and then place the moved column after that // position in the complete list @@ -402,9 +392,9 @@ public class GTableColumnModel public void removeColumn(TableColumn column) { completeList.remove(column); - int index = visibleList.indexOf(column); + int index = visibleColumns.indexOf(column); if (index >= 0) { - visibleList.remove(index); + visibleColumns.remove(index); // Adjust for the selection if (selectionModel != null) { selectionModel.removeIndexInterval(index, index); @@ -459,7 +449,7 @@ public class GTableColumnModel */ private void recalcWidthCache() { totalColumnWidth = 0; - for (TableColumn tableColumn : visibleList) { + for (TableColumn tableColumn : visibleColumns.getColumns()) { totalColumnWidth += tableColumn.getWidth(); } } @@ -471,7 +461,7 @@ public class GTableColumnModel void restoreState(List newCompleteList, List newSettingsList, List newVisibleList) { this.completeList = newCompleteList; - this.visibleList = newVisibleList; + this.visibleColumns = new VisibleColumns(newVisibleList); TableModel model = table.getModel(); if (model instanceof ConfigurableColumnTableModel) { @@ -484,10 +474,6 @@ public class GTableColumnModel configurableModel.setAllColumnSettings(columnIndexAndSettings); } - // TODO: at some point in the future (like a year or more) we can remove this, when - // we know the new code below it works -// fireColumnMarginChanged(); // let the system know to rebuild the GUI (Java HACK!) - // signal a change; we've added/removed columns, but we don't need to be specific TableColumnModelEvent e = new TableColumnModelEvent(this, 0, getColumnCount() - 1); fireColumnAdded(e); @@ -515,6 +501,74 @@ public class GTableColumnModel return oldValue; } + /* + * A small class to provide a method to quickly see if a column is visible by calling contains + * on a hash set + */ + private class VisibleColumns { + private Set visibleSet = new HashSet<>(); + private List visibleList = new ArrayList<>(); + + public VisibleColumns() { + } + + public VisibleColumns(List newVisibleList) { + this.visibleList = newVisibleList; + visibleSet.addAll(visibleList); + } + + List getColumns() { + return visibleList; + } + + int size() { + return visibleList.size(); + } + + public void remove(TableColumn column) { + visibleList.remove(column); + visibleSet.remove(column); + } + + public void add(TableColumn column) { + visibleList.add(column); + visibleSet.add(column); + } + + public Enumeration toEnumeration() { + return Collections.enumeration(visibleList); + } + + public TableColumn get(int index) { + return visibleList.get(index); + } + + public int indexOf(TableColumn column) { + return visibleList.indexOf(column); + } + + public TableColumn remove(int index) { + + TableColumn column = visibleList.remove(index); + visibleSet.remove(column); + return column; + } + + public void add(int insertIndex, TableColumn column) { + visibleList.add(insertIndex, column); + visibleSet.add(column); + } + + void clear() { + visibleList.clear(); + visibleSet.clear(); + } + + boolean contains(TableColumn c) { + return visibleSet.contains(c); + } + } + //================================================================================================== // Listener and event methods //================================================================================================== diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java index 582572e2d7..9c022c71be 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java @@ -40,7 +40,7 @@ public interface SortedTableModel extends TableModel { public boolean isSortable(int columnIndex); /** - * Returns the column index that is the primary sorted column + * Returns the column index that is the primary sorted column; -1 if no column is sorted * * @return the index */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupRowComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupComparator.java similarity index 59% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupRowComparator.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupComparator.java index 4605b05873..b138db74c1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupRowComparator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupComparator.java @@ -20,6 +20,7 @@ import java.util.Comparator; import docking.widgets.table.*; import ghidra.docking.settings.Settings; import ghidra.util.table.column.GColumnRenderer; +import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode; /** * A special version of the backup comparator that uses the column's rendered value for @@ -28,25 +29,40 @@ import ghidra.util.table.column.GColumnRenderer; * * @param the row type */ -public class ColumnRenderedValueBackupRowComparator implements Comparator { +public class ColumnRenderedValueBackupComparator implements Comparator { protected int sortColumn; protected DynamicColumnTableModel model; - public ColumnRenderedValueBackupRowComparator(DynamicColumnTableModel model, + // columns do not support sorting via their rendered value if the are marked + // for column filtering only + private boolean supportsColumnSorting = true; + + public ColumnRenderedValueBackupComparator(DynamicColumnTableModel model, int sortColumn) { this.model = model; this.sortColumn = sortColumn; + + DynamicTableColumn column = model.getColumn(sortColumn); + @SuppressWarnings("unchecked") + GColumnRenderer renderer = (GColumnRenderer) column.getColumnRenderer(); + if (renderer != null) { + if (renderer.getColumnConstraintFilterMode() == ColumnConstraintFilterMode.USE_COLUMN_CONSTRAINTS_ONLY) { + // this implies that the column has signaled that it does not support + // filtering/sorting using its rendered value + supportsColumnSorting = false; + } + } } @Override - public int compare(T t1, T t2) { - if (t1 == t2) { + public int compare(Object c1, Object c2) { + if (c1 == c2) { 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); @@ -59,19 +75,23 @@ public class ColumnRenderedValueBackupRowComparator implements Comparator // 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; + } DynamicTableColumn column = model.getColumn(sortColumn); GColumnRenderer renderer = (GColumnRenderer) column.getColumnRenderer(); - Object o = model.getColumnValueForRow(t, sortColumn); 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 protected Object getColumnValue(T t) { return model.getColumnValueForRow(t, sortColumn); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowToColumnComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowBasedColumnComparator.java similarity index 88% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowToColumnComparator.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowBasedColumnComparator.java index 648437cfb0..d6ad47692a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowToColumnComparator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowBasedColumnComparator.java @@ -27,12 +27,12 @@ import docking.widgets.table.TableComparators; * * @param the row type */ -public class RowToColumnComparator implements Comparator { +public class RowBasedColumnComparator implements Comparator { protected RowObjectTableModel model; protected int sortColumn; protected Comparator columnComparator; - protected Comparator backupRowComparator = TableComparators.getNoSortComparator(); + protected Comparator backupRowComparator = TableComparators.getNoSortComparator(); /** * Constructs this class with the given column comparator that will get called after the @@ -42,7 +42,7 @@ public class RowToColumnComparator implements Comparator { * @param sortColumn the column being sorted * @param comparator the column comparator to use for sorting */ - public RowToColumnComparator(RowObjectTableModel model, int sortColumn, + public RowBasedColumnComparator(RowObjectTableModel model, int sortColumn, Comparator comparator) { this.model = model; this.sortColumn = sortColumn; @@ -59,8 +59,8 @@ public class RowToColumnComparator implements Comparator { * @param comparator the column comparator to use for sorting * @param backupRowComparator the backup row comparator */ - public RowToColumnComparator(RowObjectTableModel model, int sortColumn, - Comparator comparator, Comparator backupRowComparator) { + public RowBasedColumnComparator(RowObjectTableModel model, int sortColumn, + Comparator comparator, Comparator backupRowComparator) { this.model = model; this.sortColumn = sortColumn; this.columnComparator = Objects.requireNonNull(comparator); @@ -97,7 +97,7 @@ public class RowToColumnComparator implements Comparator { // 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(value1, value2); } protected Object getColumnValue(T t) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java index a81703e55f..6f903110ea 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java @@ -15,16 +15,16 @@ */ package docking.widgets.table.threaded; -import docking.widgets.table.sort.ColumnRenderedValueBackupRowComparator; -import docking.widgets.table.sort.RowToColumnComparator; +import docking.widgets.table.sort.ColumnRenderedValueBackupComparator; +import docking.widgets.table.sort.RowBasedColumnComparator; /** - * A version of {@link ColumnRenderedValueBackupRowComparator} that uses the + * A version of {@link ColumnRenderedValueBackupComparator} that uses the * {@link ThreadedTableModel}'s cache for column lookups * * @param the row type */ -public class ThreadedBackupRowComparator extends ColumnRenderedValueBackupRowComparator { +public class ThreadedBackupRowComparator extends ColumnRenderedValueBackupComparator { private ThreadedTableModel threadedModel; @@ -34,7 +34,7 @@ public class ThreadedBackupRowComparator extends ColumnRenderedValueBackupRow * * @param model the table model using this comparator * @param sortColumn the column being sorted - * @see RowToColumnComparator + * @see RowBasedColumnComparator */ public ThreadedBackupRowComparator(ThreadedTableModel model, int sortColumn) { super(model, sortColumn); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java index c37d058e8c..1164b843c7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java @@ -17,7 +17,7 @@ package docking.widgets.table.threaded; import java.util.Comparator; -import docking.widgets.table.sort.RowToColumnComparator; +import docking.widgets.table.sort.RowBasedColumnComparator; /** * A comparator for comparing table column values for threaded table models. This comparator @@ -25,7 +25,7 @@ import docking.widgets.table.sort.RowToColumnComparator; * * @param the row type */ -public class ThreadedTableColumnComparator extends RowToColumnComparator { +public class ThreadedTableColumnComparator extends RowBasedColumnComparator { private ThreadedTableModel threadedModel; /** @@ -35,7 +35,7 @@ public class ThreadedTableColumnComparator extends RowToColumnComparator { * @param model the table model using this comparator * @param sortColumn the column being sorted * @param comparator the column comparator to use for sorting - * @see RowToColumnComparator + * @see RowBasedColumnComparator */ public ThreadedTableColumnComparator(ThreadedTableModel model, int sortColumn, Comparator comparator) { @@ -52,10 +52,10 @@ public class ThreadedTableColumnComparator extends RowToColumnComparator { * @param sortColumn the column being sorted * @param comparator the column comparator to use for sorting * @param backupRowComparator the backup row comparator - * @see RowToColumnComparator + * @see RowBasedColumnComparator */ public ThreadedTableColumnComparator(ThreadedTableModel model, int sortColumn, - Comparator comparator, Comparator backupRowComparator) { + Comparator comparator, Comparator backupRowComparator) { super(model, sortColumn, comparator, backupRowComparator); this.threadedModel = model; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java index 3357ce3f55..7b72a04705 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java @@ -214,7 +214,37 @@ public abstract class ThreadedTableModel protected abstract void doLoad(Accumulator 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. + * + *

Performance Notes + *

    + *
  • 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 if the + * key (the row object) is {@link Comparable}. If the key is not comparable, then + * the collision lookups will be linear. So, make your row objects comparable + * for maximum speed when your table size becomes large (for small tables there + * is no observable impact). + *
  • Even if your row objects are comparable, relying on this table model to convert your + * row object into column values can be slow for large tables. 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. + *
+ * + * @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> cachedColumnValues = threadLocalColumnCache.get(); if (cachedColumnValues == null) { @@ -239,7 +269,7 @@ public abstract class ThreadedTableModel } void initializeCache() { - threadLocalColumnCache.set(new LRUMap>(20000)); + threadLocalColumnCache.set(new LRUMap>(1000000)); } void clearCache() { @@ -747,10 +777,6 @@ public abstract class ThreadedTableModel return maxUpdateDelayMillis; } - protected Class getSortedColumnClass(int columnIndex) { - return getColumnClass(columnIndex); - } - ThreadedTableModelUpdateMgr getUpdateManager() { return updateManager; } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/TestThread.java b/Ghidra/Framework/Generic/src/main/java/generic/test/TestThread.java index 854d097bc3..4165578f9d 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/TestThread.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/TestThread.java @@ -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) { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/LRUMap.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/LRUMap.java index 305ae36342..77b812fb71 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/LRUMap.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/LRUMap.java @@ -48,7 +48,7 @@ public class LRUMap implements Map { protected HashMap> map; private int cacheSize; private Entry head; - private long modificationID = 0; + private volatile long modificationID = 0; public LRUMap(int cacheSize) { this.cacheSize = cacheSize; @@ -267,7 +267,7 @@ public class LRUMap implements Map { /** * 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 eldest) { // this is just a way for subclasses to know when items are removed from the cache diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/CodeSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/CodeSymbol.java index b3de9fac9a..8c4d1a26ef 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/CodeSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/CodeSymbol.java @@ -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 diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/FunctionSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/FunctionSymbol.java index af59a47c0e..fd1c617240 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/FunctionSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/FunctionSymbol.java @@ -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; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/GlobalVariableSymbolDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/GlobalVariableSymbolDB.java index a446072765..d641e86939 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/GlobalVariableSymbolDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/GlobalVariableSymbolDB.java @@ -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(); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java index 0808642683..749aee71c8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java @@ -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 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(); + } + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java index 40409ac8dc..7096df0382 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java @@ -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 { SymbolType type = s.getSymbolType(); return type == type1; } - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/VariableSymbolDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/VariableSymbolDB.java index 6c497eb5cc..2bea32aa44 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/VariableSymbolDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/VariableSymbolDB.java @@ -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 diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java index b9f90fbf95..82abf5d987 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java @@ -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); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java index 5d41b1f80f..01f99db339 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java @@ -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"; } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/SymbolTablePluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/SymbolTablePluginScreenShots.java index be99524a36..508a6656b3 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/SymbolTablePluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/SymbolTablePluginScreenShots.java @@ -96,7 +96,7 @@ public class SymbolTablePluginScreenShots extends GhidraScreenShotGenerator { ComponentProvider provider = getProvider("Symbol Table"); tool.showComponentProvider(provider, true); - moveProviderToItsOwnWindow(provider, 950, 750); + moveProviderToItsOwnWindow(provider, 950, 400); GTable table = getTable(provider); setColumnSizes(table); @@ -172,13 +172,28 @@ public class SymbolTablePluginScreenShots extends GhidraScreenShotGenerator { TableColumn column = columnModel.getColumn(i); Object headerValue = column.getHeaderValue(); if ("Name".equals(headerValue)) { - column.setPreferredWidth(175); + column.setPreferredWidth(300); } else if ("Reference Count".equals(headerValue)) { - column.setPreferredWidth(15); + column.setPreferredWidth(25); } else if ("Offcut Ref Count".equals(headerValue)) { - column.setPreferredWidth(15); + column.setPreferredWidth(25); + } + else if ("Namespace".equals(headerValue)) { + column.setPreferredWidth(160); + } + else if ("Location".equals(headerValue)) { + column.setPreferredWidth(170); + } + else if ("Namespace".equals(headerValue)) { + column.setPreferredWidth(170); + } + else if ("Source".equals(headerValue)) { + column.setPreferredWidth(170); + } + else if ("Type".equals(headerValue)) { + column.setPreferredWidth(170); } } });

Symbol type (Function, External, Class, etc).
DatatypeDatatype (i.e., byte, float, etc.) applied at symbol - address.
Namespace