mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
Merge remote-tracking branch
'origin/GP-0-dragonmacher-test-fixes-4-8-21--SQUASHED'
This commit is contained in:
commit
965dfcaa9b
17 changed files with 986 additions and 246 deletions
|
@ -615,8 +615,4 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Symbol createSymbolPlaceholder(Address address, long id) {
|
|
||||||
return new DBTracePlaceholderSymbol(symbolManager, id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.trace.database.symbol;
|
|
||||||
|
|
||||||
import ghidra.program.model.symbol.SymbolType;
|
|
||||||
|
|
||||||
public class DBTracePlaceholderSymbol extends AbstractDBTraceSymbol {
|
|
||||||
protected final long id;
|
|
||||||
|
|
||||||
public DBTracePlaceholderSymbol(DBTraceSymbolManager manager, long id) {
|
|
||||||
super(manager, null, null);
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getID() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SymbolType getSymbolType() {
|
|
||||||
return SymbolType.getSymbolType(DBTraceSymbolManager.unpackTypeID(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPrimary() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setPrimary() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getObject() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -44,6 +44,7 @@ public class OneShotAnalysisCommand extends BackgroundCommand {
|
||||||
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
|
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
|
||||||
Program program = (Program) obj;
|
Program program = (Program) obj;
|
||||||
try {
|
try {
|
||||||
|
monitor.setMessage(analyzer.getName());
|
||||||
return analyzer.added(program, set, monitor, log);
|
return analyzer.added(program, set, monitor, log);
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.symtable;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.symbol.*;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that allows the symbol table plugin to locate deleted items by id
|
||||||
|
*/
|
||||||
|
class ProxySymbol implements Symbol {
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
private Address address;
|
||||||
|
|
||||||
|
ProxySymbol(long id, Address address) {
|
||||||
|
this.id = id;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(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 int hashCode() {
|
||||||
|
return (int) id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getID() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SymbolType getSymbolType() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgramLocation getProgramLocation() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isExternal() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPrimary() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidParent(Namespace parent) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getPath() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Program getProgram() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName(boolean includeNamespace) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Namespace getParentNamespace() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Symbol getParentSymbol() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDescendant(Namespace namespace) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getReferenceCount() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMultipleReferences() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasReferences() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reference[] getReferences(TaskMonitor monitor) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reference[] getReferences() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String newName, SourceType source) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNamespace(Namespace newNamespace) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNameAndNamespace(String newName, Namespace newNamespace, SourceType source) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPinned() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPinned(boolean pinned) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDynamic() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setPrimary() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isExternalEntryPoint() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGlobal() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSource(SourceType source) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SourceType getSource() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDeleted() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "[id=" + id + ", address=" + address + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,6 +110,10 @@ public class SymbolReferenceModel extends AddressBasedTableModel<Reference> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setProgram(Program prog) {
|
public void setProgram(Program prog) {
|
||||||
|
if (isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (prog == null) {
|
if (prog == null) {
|
||||||
super.setProgram(null);
|
super.setProgram(null);
|
||||||
refManager = null;
|
refManager = null;
|
||||||
|
|
|
@ -15,71 +15,253 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.symtable;
|
package ghidra.app.plugin.core.symtable;
|
||||||
|
|
||||||
|
import static docking.widgets.table.AddRemoveListItem.Type.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import docking.widgets.table.AddRemoveListItem;
|
import docking.widgets.table.AddRemoveListItem;
|
||||||
import docking.widgets.table.threaded.TableAddRemoveStrategy;
|
import docking.widgets.table.TableSortingContext;
|
||||||
import docking.widgets.table.threaded.TableData;
|
import docking.widgets.table.threaded.*;
|
||||||
|
import ghidra.program.model.symbol.Symbol;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This strategy attempts to optimize removal of db objects that have been deleted. The issue with
|
* The symbol table users a {@link ThreadedTableModel}. This model does not correctly function
|
||||||
* deleted db objects is that they may no longer have their attributes, which means we cannot
|
* with data that can change outside of the table. The symbol table's row objects are the
|
||||||
* use any of those attributes that may have been used as the basis for sorting. We use the
|
* {@link Symbol} db objects. These db objects can be changed by the user and by analysis
|
||||||
* table's sort to perform a binary search of existing symbols for removal. If the binary search
|
* while table is loaded. The problem with this is that the table's sort can be broken when
|
||||||
* does not work, then removal operations will require slow list traversal. Additionally,
|
* symbols are to be added, removed or re-inserted, as this process requires a binary search which
|
||||||
* some clients use proxy objects in add/remove list to signal which object needs to be removed,
|
* will be broken if the criteria used to sort the data has changed. Effectively, a symbol
|
||||||
* since the original object is no longer available to the client. Using these proxy objects
|
* change can break the binary search if that symbol stays in a previously sorted position, but
|
||||||
* in a binary search may lead to exceptions if the proxy has unsupported methods called when
|
* has updated data that would put the symbol in a new position if sorted again. For example,
|
||||||
* searching.
|
* if the table is sorted on name and the name of a symbol changes, then future uses of the
|
||||||
|
* binary search will be broken while that symbol is still in the position that matches its old
|
||||||
|
* name.
|
||||||
|
* <p>
|
||||||
|
* This issue has been around for quite some time. To completely fix this issue, each row object
|
||||||
|
* of the symbol table would need to be immutable, at least on the sort criteria. We could fix
|
||||||
|
* this in the future if the *mostly correct* sorting behavior is not good enough. For now, the
|
||||||
|
* client can trigger a re-sort (e.g., by opening and closing the table) to fix the slightly
|
||||||
|
* out-of-sort data.
|
||||||
|
* <p>
|
||||||
|
* The likelihood of the sort being inconsistent now relates directly to how many changed symbols
|
||||||
|
* are in the table at the time of an insert. The more changes symbols, the higher the chance
|
||||||
|
* of a stale/misplaced symbol being used during a binary search, thus producing an invalid insert
|
||||||
|
* position.
|
||||||
|
* <p>
|
||||||
|
* This strategy is setup to mitigate the number of invalid symbols in the table at the
|
||||||
|
* time the inserts are applied. The basic workflow of this algorithm is:
|
||||||
|
* <pre>
|
||||||
|
* 1) condense the add / remove requests to remove duplicate efforts
|
||||||
|
* 2) process all removes first
|
||||||
|
* --all pure removes
|
||||||
|
* --all removes as part of a re-insert
|
||||||
|
* 3) process all items that failed to remove due to the sort data changing
|
||||||
|
* 4) process all adds (this step will fail if the data contains mis-sorted items)
|
||||||
|
* --all adds as part of a re-insert
|
||||||
|
* --all pure adds
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* <P>This strategy will has guilty knowledge of client proxy object usage. The proxy objects
|
* Step 3, processing failed removals, is done to avoid a brute force lookup at each removal
|
||||||
|
* request.
|
||||||
|
*
|
||||||
|
* <P>This strategy has knowledge of client proxy object usage. The proxy objects
|
||||||
* are coded such that the {@code hashCode()} and {@code equals()} methods will match those
|
* are coded such that the {@code hashCode()} and {@code equals()} methods will match those
|
||||||
* methods of the data's real objects.
|
* methods of the data's real objects.
|
||||||
*
|
|
||||||
* @param <T> the row type
|
|
||||||
*/
|
*/
|
||||||
public class SymbolTableAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> {
|
public class SymbolTableAddRemoveStrategy implements TableAddRemoveStrategy<Symbol> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> tableData,
|
public void process(List<AddRemoveListItem<Symbol>> addRemoveList, TableData<Symbol> tableData,
|
||||||
TaskMonitor monitor) throws CancelledException {
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
Set<AddRemoveListItem<Symbol>> items = coalesceAddRemoveItems(addRemoveList);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Hash map the existing values so that we can use any object inside the add/remove list
|
// Hash map the existing values so that we can use any object inside the add/remove list
|
||||||
// as a key into this map to get the matching existing value.
|
// as a key into this map to get the matching existing value. Using the existing value
|
||||||
|
// enables the binary search to work when the add/remove item is a proxy object, but the
|
||||||
|
// existing item still has the data used to sort it. If the sort data has changed, then
|
||||||
|
// even this step will not allow the TableData to find the item in a search.
|
||||||
//
|
//
|
||||||
Map<T, T> hashed = new HashMap<>();
|
Map<Symbol, Symbol> hashed = new HashMap<>();
|
||||||
for (T t : tableData) {
|
for (Symbol symbol : tableData) {
|
||||||
hashed.put(t, t);
|
hashed.put(symbol, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
int n = addRemoveList.size();
|
Set<Symbol> failedToRemove = new HashSet<>();
|
||||||
monitor.setMessage("Adding/Removing " + n + " items...");
|
|
||||||
|
int n = items.size();
|
||||||
|
monitor.setMessage("Removing " + n + " items...");
|
||||||
monitor.initialize(n);
|
monitor.initialize(n);
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
AddRemoveListItem<T> item = addRemoveList.get(i);
|
Iterator<AddRemoveListItem<Symbol>> it = items.iterator();
|
||||||
T value = item.getValue();
|
while (it.hasNext()) {
|
||||||
|
AddRemoveListItem<Symbol> item = it.next();
|
||||||
|
Symbol value = item.getValue();
|
||||||
if (item.isChange()) {
|
if (item.isChange()) {
|
||||||
T toRemove = hashed.get(value);
|
Symbol toRemove = hashed.remove(value);
|
||||||
if (toRemove != null) {
|
remove(tableData, toRemove, failedToRemove);
|
||||||
tableData.remove(toRemove);
|
monitor.incrementProgress(1);
|
||||||
}
|
|
||||||
tableData.insert(value);
|
|
||||||
}
|
}
|
||||||
else if (item.isRemove()) {
|
else if (item.isRemove()) {
|
||||||
T toRemove = hashed.get(value);
|
Symbol toRemove = hashed.remove(value);
|
||||||
if (toRemove != null) {
|
remove(tableData, toRemove, failedToRemove);
|
||||||
tableData.remove(toRemove);
|
it.remove();
|
||||||
}
|
}
|
||||||
|
monitor.checkCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failedToRemove.isEmpty()) {
|
||||||
|
int size = failedToRemove.size();
|
||||||
|
String message = size == 1 ? "1 old symbol..." : size + " old symbols...";
|
||||||
|
monitor.setMessage("Removing " + message);
|
||||||
|
|
||||||
|
tableData.process((data, sortContext) -> {
|
||||||
|
return expungeLostItems(failedToRemove, data, sortContext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
n = items.size();
|
||||||
|
monitor.setMessage("Adding " + n + " items...");
|
||||||
|
it = items.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
AddRemoveListItem<Symbol> item = it.next();
|
||||||
|
Symbol value = item.getValue();
|
||||||
|
if (item.isChange()) {
|
||||||
|
tableData.insert(value);
|
||||||
|
hashed.put(value, value);
|
||||||
}
|
}
|
||||||
else if (item.isAdd()) {
|
else if (item.isAdd()) {
|
||||||
tableData.insert(value);
|
tableData.insert(value);
|
||||||
|
hashed.put(value, value);
|
||||||
}
|
}
|
||||||
monitor.checkCanceled();
|
monitor.checkCanceled();
|
||||||
monitor.setProgress(i);
|
monitor.incrementProgress(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor.setMessage("Done adding/removing");
|
monitor.setMessage("Done adding/removing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<AddRemoveListItem<Symbol>> coalesceAddRemoveItems(
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoveList) {
|
||||||
|
|
||||||
|
Map<Long, AddRemoveListItem<Symbol>> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (AddRemoveListItem<Symbol> item : addRemoveList) {
|
||||||
|
|
||||||
|
if (item.isChange()) {
|
||||||
|
handleChange(item, map);
|
||||||
|
}
|
||||||
|
else if (item.isAdd()) {
|
||||||
|
handleAdd(item, map);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleRemove(item, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HashSet<>(map.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAdd(AddRemoveListItem<Symbol> item,
|
||||||
|
Map<Long, AddRemoveListItem<Symbol>> map) {
|
||||||
|
|
||||||
|
long id = item.getValue().getID();
|
||||||
|
AddRemoveListItem<Symbol> existing = map.get(id);
|
||||||
|
if (existing == null) {
|
||||||
|
map.put(id, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.isChange()) {
|
||||||
|
return; // change -> add; keep the change
|
||||||
|
}
|
||||||
|
if (existing.isAdd()) {
|
||||||
|
return; // already an add
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove -> add; make a change
|
||||||
|
map.put(id, new AddRemoveListItem<>(CHANGE, existing.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRemove(AddRemoveListItem<Symbol> item,
|
||||||
|
Map<Long, AddRemoveListItem<Symbol>> map) {
|
||||||
|
|
||||||
|
long id = item.getValue().getID();
|
||||||
|
AddRemoveListItem<Symbol> existing = map.get(id);
|
||||||
|
if (existing == null) {
|
||||||
|
map.put(id, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.isChange()) {
|
||||||
|
map.put(id, item); // change -> remove; just do the remove
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (existing.isRemove()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add -> remove; do no work
|
||||||
|
map.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleChange(AddRemoveListItem<Symbol> item,
|
||||||
|
Map<Long, AddRemoveListItem<Symbol>> map) {
|
||||||
|
|
||||||
|
long id = item.getValue().getID();
|
||||||
|
AddRemoveListItem<Symbol> existing = map.get(id);
|
||||||
|
if (existing == null) {
|
||||||
|
map.put(id, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existing.isChange()) {
|
||||||
|
// either add or remove followed by a change; keep the change
|
||||||
|
map.put(id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we had a change followed by a change; keep just 1 change
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(TableData<Symbol> tableData, Symbol symbol, Set<Symbol> failedToRemove) {
|
||||||
|
if (symbol == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tableData.remove(symbol)) {
|
||||||
|
failedToRemove.add(symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes the given set of items that were unsuccessfully removed from the table as part of
|
||||||
|
* the add/remove process. These items could not be removed because some part of their
|
||||||
|
* state has changed such that the binary search performed during the normal remove process
|
||||||
|
* cannot locate the item in the table data. This algorithm will check the given set of
|
||||||
|
* items against the entire list of table data, locating the item to be removed.
|
||||||
|
*/
|
||||||
|
private List<Symbol> expungeLostItems(Set<Symbol> toRemove, List<Symbol> data,
|
||||||
|
TableSortingContext<Symbol> sortContext) {
|
||||||
|
|
||||||
|
if (sortContext.isUnsorted()) {
|
||||||
|
// this can happen if the data is unsorted and we were asked to remove an item that
|
||||||
|
// was never in the table for some reason
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy to a new list those items that are not marked for removal. This saves the
|
||||||
|
// list move its items every time a remove takes place
|
||||||
|
List<Symbol> newList = new ArrayList<>(data.size() - toRemove.size());
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
Symbol rowObject = data.get(i);
|
||||||
|
if (!toRemove.contains(rowObject)) {
|
||||||
|
newList.add(rowObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ class SymbolTableModel extends AddressBasedTableModel<Symbol> {
|
||||||
private Symbol lastSymbol;
|
private Symbol lastSymbol;
|
||||||
private SymbolFilter filter;
|
private SymbolFilter filter;
|
||||||
private TableAddRemoveStrategy<Symbol> deletedDbObjectAddRemoveStrategy =
|
private TableAddRemoveStrategy<Symbol> deletedDbObjectAddRemoveStrategy =
|
||||||
new SymbolTableAddRemoveStrategy<>();
|
new SymbolTableAddRemoveStrategy();
|
||||||
|
|
||||||
SymbolTableModel(SymbolProvider provider, PluginTool tool) {
|
SymbolTableModel(SymbolProvider provider, PluginTool tool) {
|
||||||
super("Symbols", tool, null, null);
|
super("Symbols", tool, null, null);
|
||||||
|
|
|
@ -609,8 +609,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
protected void doRun() {
|
protected void doRun() {
|
||||||
|
|
||||||
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
||||||
Symbol removedSymbol =
|
Symbol removedSymbol = new ProxySymbol(symbolId, address);
|
||||||
symbolTable.createSymbolPlaceholder(address, symbolId);
|
|
||||||
symProvider.symbolRemoved(removedSymbol);
|
symProvider.symbolRemoved(removedSymbol);
|
||||||
refProvider.symbolRemoved(removedSymbol);
|
refProvider.symbolRemoved(removedSymbol);
|
||||||
|
|
||||||
|
@ -741,7 +740,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
|
|
||||||
if (symProvider.isShowingDynamicSymbols()) {
|
if (symProvider.isShowingDynamicSymbols()) {
|
||||||
long id = symbolTable.getDynamicSymbolID(reference.getToAddress());
|
long id = symbolTable.getDynamicSymbolID(reference.getToAddress());
|
||||||
Symbol removedSymbol = symbolTable.createSymbolPlaceholder(toAddr, id);
|
Symbol removedSymbol = new ProxySymbol(id, toAddr);
|
||||||
symProvider.symbolRemoved(removedSymbol);
|
symProvider.symbolRemoved(removedSymbol);
|
||||||
refProvider.symbolRemoved(removedSymbol);
|
refProvider.symbolRemoved(removedSymbol);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,375 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.symtable;
|
||||||
|
|
||||||
|
import static docking.widgets.table.AddRemoveListItem.Type.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import docking.widgets.table.*;
|
||||||
|
import docking.widgets.table.threaded.TestTableData;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.TestAddress;
|
||||||
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class SymbolTableAddRemoveStrategyTest {
|
||||||
|
|
||||||
|
private SymbolTableAddRemoveStrategy strategy;
|
||||||
|
private SpyTableData spyTableData;
|
||||||
|
private List<Symbol> modelData;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
strategy = new SymbolTableAddRemoveStrategy();
|
||||||
|
modelData = createModelData();
|
||||||
|
spyTableData = createTableData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpyTableData createTableData() {
|
||||||
|
Comparator<Symbol> comparator = (s1, s2) -> {
|
||||||
|
return s1.getName().compareTo(s2.getName());
|
||||||
|
};
|
||||||
|
TableSortState sortState = TableSortState.createDefaultSortState(0);
|
||||||
|
TableSortingContext<Symbol> sortContext =
|
||||||
|
new TableSortingContext<>(sortState, comparator);
|
||||||
|
|
||||||
|
modelData.sort(comparator);
|
||||||
|
|
||||||
|
return new SpyTableData(modelData, sortContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Symbol> createModelData() {
|
||||||
|
List<Symbol> data = new ArrayList<>();
|
||||||
|
data.add(new TestSymbol(1, new TestAddress(101)));
|
||||||
|
data.add(new TestSymbol(2, new TestAddress(102)));
|
||||||
|
data.add(new TestSymbol(3, new TestAddress(103)));
|
||||||
|
data.add(new TestSymbol(4, new TestAddress(104)));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemove_DifferentInstance_SameId() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol s = new TestSymbol(1, new TestAddress(101));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(0, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsert_NewSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol newSymbol = new TestSymbol(10, new TestAddress(1010));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
assertEquals(0, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertAndRemove_NewSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol newSymbol = new TestSymbol(10, new TestAddress(1010));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, newSymbol));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// no work was done, since the insert was followed by a remove
|
||||||
|
assertEquals(0, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(0, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChange_NewSymbol() throws Exception {
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol newSymbol = new TestSymbol(10, new TestAddress(1010));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, newSymbol));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// no remove, since the symbol was not in the table
|
||||||
|
assertEquals(0, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChange_ExisingSymbol() throws Exception {
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// no remove, since the symbol was not in the table
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAndInsert_NewSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol newSymbol = new TestSymbol(10, new TestAddress(1010));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, newSymbol));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the remove does not happen, since the time was not in the table
|
||||||
|
assertEquals(0, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAndInsert_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the remove does not happen, since the time was not in the table
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChangeAndInsert_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the insert portions get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChangeAndRemove_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the remove portions get coalesced; no insert takes place
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(0, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChangeAndChange_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the changes get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAndRemove_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the removes get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(0, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertAndInsert_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the inserts get coalesced
|
||||||
|
assertEquals(0, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertAndChange_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the insert portions get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAndChange_ExistingSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
Symbol s = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the remove portions get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLostItems_Remove() throws Exception {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test that symbols get removed when the data up on which they are sorted changes before
|
||||||
|
// the removal takes place
|
||||||
|
//
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol symbol = (TestSymbol) modelData.get(0);
|
||||||
|
symbol.setName("UpdatedName");
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, symbol));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the insert portions get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(0, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLostItems_Change() throws Exception {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test that symbols get removed when the data up on which they are sorted changes before
|
||||||
|
// the removal takes place
|
||||||
|
//
|
||||||
|
List<AddRemoveListItem<Symbol>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestSymbol symbol = (TestSymbol) modelData.get(0);
|
||||||
|
symbol.setName("UpdatedName");
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, symbol));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
// the insert portions get coalesced
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(1, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Inner Classes
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
private class SpyTableData extends TestTableData<Symbol> {
|
||||||
|
|
||||||
|
private int removeCount;
|
||||||
|
private int insertCount;
|
||||||
|
|
||||||
|
SpyTableData(List<Symbol> data, TableSortingContext<Symbol> sortContext) {
|
||||||
|
super(data, sortContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Symbol t) {
|
||||||
|
removeCount++;
|
||||||
|
return super.remove(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insert(Symbol value) {
|
||||||
|
insertCount++;
|
||||||
|
super.insert(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getRemoveCount() {
|
||||||
|
return removeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getInsertCount() {
|
||||||
|
return insertCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSymbol extends ProxySymbol {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
TestSymbol(long id, Address address) {
|
||||||
|
super(id, address);
|
||||||
|
name = id + "@" + address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,28 +152,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@Test
|
||||||
public void testColumnDiscovery() throws Exception {
|
public void testColumnDiscovery() throws Exception {
|
||||||
//
|
//
|
||||||
|
@ -449,20 +427,19 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
// 1 Function label removed (1 dynamic added at function entry)
|
// 1 Function label removed (1 dynamic added at function entry)
|
||||||
// Locals were promoted to global.
|
// Locals were promoted to global.
|
||||||
assertEquals(rowCount, symbolTable.getRowCount());
|
|
||||||
int newDynamicSymbolRow = findRow("SUB_00000052");
|
int newDynamicSymbolRow = findRow("SUB_00000052");
|
||||||
assertFalse(newDynamicSymbolRow == -1);
|
assertNotEquals(-1, newDynamicSymbolRow);
|
||||||
|
assertEquals(rowCount, symbolTable.getRowCount());
|
||||||
|
|
||||||
int anotherLocal_RowIndex = findRow("AnotherLocal");
|
int anotherLocal_RowIndex = findRow("AnotherLocal");
|
||||||
selectRow(anotherLocal_RowIndex);
|
selectRow(anotherLocal_RowIndex);
|
||||||
|
|
||||||
performAction(deleteAction, true);
|
performAction(deleteAction, true);
|
||||||
|
waitForNotBusy();
|
||||||
anotherLocalSymbol = getUniqueSymbol(program, "AnotherLocal");
|
anotherLocalSymbol = getUniqueSymbol(program, "AnotherLocal");
|
||||||
assertNull("Delete action did not delete symbol: " + anotherLocalSymbol,
|
assertNull("Delete action did not delete symbol: " + anotherLocalSymbol,
|
||||||
anotherLocalSymbol);// AnotherLocal should have been promoted to global since user defined.
|
anotherLocalSymbol);// AnotherLocal should have been promoted to global since user defined.
|
||||||
|
|
||||||
waitForNotBusy();
|
|
||||||
|
|
||||||
// 1 Data label removed
|
// 1 Data label removed
|
||||||
int expected = rowCount - 1;
|
int expected = rowCount - 1;
|
||||||
assertEquals(expected, symbolTable.getRowCount());
|
assertEquals(expected, symbolTable.getRowCount());
|
||||||
|
@ -654,6 +631,29 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals(rowCount + 1, symbolModel.getRowCount());
|
assertEquals(rowCount + 1, symbolModel.getRowCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRenameUpdatesSort() throws Exception {
|
||||||
|
|
||||||
|
openProgram("sample");
|
||||||
|
|
||||||
|
waitForNotBusy();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions: 'ghidra', 'func_with_parms'
|
||||||
|
//
|
||||||
|
assertEquals(25, symbolTable.getRowCount());
|
||||||
|
|
||||||
|
Symbol symbol = getUniqueSymbol(program, "ghidra");
|
||||||
|
int rowIndex = indexOf(symbol);
|
||||||
|
|
||||||
|
setName(symbol, "____ghidra", SourceType.DEFAULT);
|
||||||
|
assertEquals(25, symbolTable.getRowCount());
|
||||||
|
|
||||||
|
int updatedRowIndex = indexOf(symbol);
|
||||||
|
assertNotEquals(rowIndex, updatedRowIndex);
|
||||||
|
assertEquals(0, updatedRowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultFunctionToNamedFunctionWithFilterOn() throws Exception {
|
public void testDefaultFunctionToNamedFunctionWithFilterOn() throws Exception {
|
||||||
openProgram("sample");
|
openProgram("sample");
|
||||||
|
@ -758,20 +758,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals(3, refCount.intValue());
|
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
|
@Test
|
||||||
public void testUpdateOnProgramRestore() throws Exception {
|
public void testUpdateOnProgramRestore() throws Exception {
|
||||||
openProgram("sample");
|
openProgram("sample");
|
||||||
|
@ -1134,6 +1120,46 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// Helper methods
|
// Helper methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private int indexOf(Symbol symbol) {
|
||||||
|
return runSwing(() -> symbolModel.getRowIndex(symbol));
|
||||||
|
}
|
||||||
|
|
||||||
private void removeReference(String from, String to) {
|
private void removeReference(String from, String to) {
|
||||||
|
|
||||||
ReferenceManager rm = program.getReferenceManager();
|
ReferenceManager rm = program.getReferenceManager();
|
||||||
|
@ -1634,9 +1660,13 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int findRow(String symbolName, String namespace) {
|
private int findRow(String symbolName, String namespace) {
|
||||||
|
waitForSwing();
|
||||||
int max = symbolTable.getRowCount();
|
int max = symbolTable.getRowCount();
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
Symbol s = (Symbol) symbolTable.getValueAt(i, SymbolTableModel.LABEL_COL);
|
Symbol s = (Symbol) symbolTable.getValueAt(i, SymbolTableModel.LABEL_COL);
|
||||||
|
if (s == null) {
|
||||||
|
continue; // symbol deleted
|
||||||
|
}
|
||||||
if (symbolName.equals(s.getName()) &&
|
if (symbolName.equals(s.getName()) &&
|
||||||
namespace.equals(s.getParentNamespace().getName())) {
|
namespace.equals(s.getParentNamespace().getName())) {
|
||||||
return i;
|
return i;
|
||||||
|
|
|
@ -15,27 +15,41 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table;
|
package docking.widgets.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that represents and add, remove or change operation for one row of a table
|
||||||
|
*
|
||||||
|
* @param <T> the row type
|
||||||
|
*/
|
||||||
public class AddRemoveListItem<T> {
|
public class AddRemoveListItem<T> {
|
||||||
private boolean isAdd;
|
|
||||||
private boolean isRemove;
|
|
||||||
private T value;
|
|
||||||
|
|
||||||
public AddRemoveListItem(boolean isAdd, boolean isRemove, T value) {
|
public enum Type {
|
||||||
this.isAdd = isAdd;
|
ADD,
|
||||||
this.isRemove = isRemove;
|
REMOVE,
|
||||||
|
CHANGE
|
||||||
|
}
|
||||||
|
|
||||||
|
private T value;
|
||||||
|
private Type type;
|
||||||
|
|
||||||
|
public AddRemoveListItem(Type type, T value) {
|
||||||
|
this.type = type;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAdd() {
|
public boolean isAdd() {
|
||||||
return isAdd;
|
return type == Type.ADD;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRemove() {
|
public boolean isRemove() {
|
||||||
return isRemove;
|
return type == Type.REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChange() {
|
public boolean isChange() {
|
||||||
return isAdd && isRemove;
|
return type == Type.CHANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getValue() {
|
public T getValue() {
|
||||||
|
@ -48,8 +62,7 @@ public class AddRemoveListItem<T> {
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
return "{\n" +
|
return "{\n" +
|
||||||
"\tvalue: " + value +",\n" +
|
"\tvalue: " + value +",\n" +
|
||||||
"\tisAdd: " + isAdd +",\n" +
|
"\ttype: " + type +",\n" +
|
||||||
"\tisRemove: " + isRemove +"\n" +
|
|
||||||
"}";
|
"}";
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package docking.widgets.table.threaded;
|
package docking.widgets.table.threaded;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
import docking.widgets.table.TableFilter;
|
import docking.widgets.table.TableFilter;
|
||||||
import docking.widgets.table.TableSortingContext;
|
import docking.widgets.table.TableSortingContext;
|
||||||
|
@ -70,7 +71,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||||
// no source; no data; no sort
|
// no source; no data; no sort
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableData(List<ROW_OBJECT> data, TableSortingContext<ROW_OBJECT> sortContext) {
|
TableData(List<ROW_OBJECT> data, TableSortingContext<ROW_OBJECT> sortContext) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.sortContext = sortContext;
|
this.sortContext = sortContext;
|
||||||
}
|
}
|
||||||
|
@ -169,16 +170,30 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// We used to have code that pass proxy objects to this class to remove items. That code
|
||||||
// At this point we have one of 2 conditions: the object is not in the list, or the object
|
// has been updated to no longer pass proxy objects. Leaving this code here for a while
|
||||||
// does not work with the current sort comparator.
|
// just in case we find another client doing the same thing.
|
||||||
//
|
// return data.remove(t);
|
||||||
// There are cases where the comparator will not work for the object handed to this method,
|
return false;
|
||||||
// such as when some db objects get deleted and the client uses a proxy object to perform
|
}
|
||||||
// the delete. To handle these odd cases, we still have to brute-force search. If this
|
|
||||||
// proves to be a bottleneck, then we can update how we handle item removal.
|
/**
|
||||||
//
|
* A generic method that allows clients to process the contents of this table data. This
|
||||||
return data.remove(t);
|
* method is not synchronized and should only be called from a {@link TableUpdateJob} or
|
||||||
|
* one of its callbacks.
|
||||||
|
*
|
||||||
|
* <P>Note: this method will do nothing if the data is not sorted.
|
||||||
|
*
|
||||||
|
* @param function the consumer of the data and the current sort context
|
||||||
|
*/
|
||||||
|
public void process(
|
||||||
|
BiFunction<List<ROW_OBJECT>, TableSortingContext<ROW_OBJECT>, List<ROW_OBJECT>> function) {
|
||||||
|
|
||||||
|
if (source != null) {
|
||||||
|
source.process(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = function.apply(data, sortContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table.threaded;
|
package docking.widgets.table.threaded;
|
||||||
|
|
||||||
|
import static docking.widgets.table.AddRemoveListItem.Type.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
@ -499,7 +501,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||||
* @param obj the object for which to schedule the update
|
* @param obj the object for which to schedule the update
|
||||||
*/
|
*/
|
||||||
public void updateObject(ROW_OBJECT obj) {
|
public void updateObject(ROW_OBJECT obj) {
|
||||||
updateManager.addRemove(new AddRemoveListItem<>(true, true, obj));
|
updateManager.addRemove(new AddRemoveListItem<>(CHANGE, obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -507,7 +509,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||||
* @param obj the object to add
|
* @param obj the object to add
|
||||||
*/
|
*/
|
||||||
public void addObject(ROW_OBJECT obj) {
|
public void addObject(ROW_OBJECT obj) {
|
||||||
updateManager.addRemove(new AddRemoveListItem<>(true, false, obj));
|
updateManager.addRemove(new AddRemoveListItem<>(ADD, obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -526,7 +528,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||||
* @param obj the object to remove
|
* @param obj the object to remove
|
||||||
*/
|
*/
|
||||||
public void removeObject(ROW_OBJECT obj) {
|
public void removeObject(ROW_OBJECT obj) {
|
||||||
updateManager.addRemove(new AddRemoveListItem<>(false, true, obj));
|
updateManager.addRemove(new AddRemoveListItem<>(REMOVE, obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateNow() {
|
protected void updateNow() {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package docking.widgets.table.threaded;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import docking.widgets.table.TableSortingContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link TableData} that can be used for testing
|
||||||
|
*
|
||||||
|
* @param <ROW_OBJECT> the row type
|
||||||
|
*/
|
||||||
|
public class TestTableData<ROW_OBJECT> extends TableData<ROW_OBJECT> {
|
||||||
|
|
||||||
|
public TestTableData(List<ROW_OBJECT> data, TableSortingContext<ROW_OBJECT> sortContext) {
|
||||||
|
super(data, sortContext);
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,6 @@ import ghidra.program.model.listing.CircularDependencyException;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.program.util.ChangeManager;
|
import ghidra.program.util.ChangeManager;
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
import ghidra.util.Lock;
|
import ghidra.util.Lock;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
@ -52,20 +51,6 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
|
||||||
private volatile String cachedName;
|
private volatile String cachedName;
|
||||||
private volatile long cachedNameModCount;
|
private volatile long cachedNameModCount;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Symbol that is just a placeholder for use when trying to find symbols by using
|
|
||||||
* {@link Symbol#getID()}. This is useful for locating symbols in Java collections when
|
|
||||||
* a symbol has been deleted and the only remaining information is that symbol's ID.
|
|
||||||
*
|
|
||||||
* @param manager the manager for the new symbol
|
|
||||||
* @param address the address of the symbol
|
|
||||||
* @param id the id of the symbol
|
|
||||||
* @return the fake symbol
|
|
||||||
*/
|
|
||||||
static SymbolDB createSymbolPlaceholder(SymbolManager manager, Address address, long id) {
|
|
||||||
return new PlaceholderSymbolDB(manager, address, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
SymbolDB(SymbolManager symbolMgr, DBObjectCache<SymbolDB> cache, Address address,
|
SymbolDB(SymbolManager symbolMgr, DBObjectCache<SymbolDB> cache, Address address,
|
||||||
DBRecord record) {
|
DBRecord record) {
|
||||||
super(cache, record.getKey());
|
super(cache, record.getKey());
|
||||||
|
@ -941,58 +926,4 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
|
||||||
this.record = record;
|
this.record = record;
|
||||||
keyChanged(record.getKey());
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2400,11 +2400,6 @@ public class SymbolManager implements SymbolTable, ManagerDB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
* 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
|
* public interface and is only intended for program API internal use. The user of this
|
||||||
|
|
|
@ -540,17 +540,6 @@ public interface SymbolTable {
|
||||||
public Namespace createNameSpace(Namespace parent, String name, SourceType source)
|
public Namespace createNameSpace(Namespace parent, String name, SourceType source)
|
||||||
throws DuplicateNameException, InvalidInputException;
|
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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given namespace to a class namespace
|
* Converts the given namespace to a class namespace
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue