GP-7137 - Updated the register picker and the Register Provider to use a

natural sort
This commit is contained in:
dragonmacher
2026-08-12 13:22:31 -04:00
parent 34b446a328
commit e728c307b7
7 changed files with 488 additions and 201 deletions

View File

@@ -41,6 +41,7 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.ProgramContext;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.datastruct.AlphaNumericComparator;
class VarnodeLocationCellEditor extends AbstractCellEditor
implements TableCellEditor, FocusableEditor {
@@ -178,7 +179,11 @@ class VarnodeLocationCellEditor extends AbstractCellEditor
RegisterDropDownSelectionDataModel registerModel =
new RegisterDropDownSelectionDataModel(registers);
registerEntryTextField = new DropDownSelectionTextField<>(registerModel);
// A smaller min delay to make the drop-down more responsive. The number of registers
// should not be large enough to make the UI sluggish as the user is typing.
int minDelay = 200;
registerEntryTextField = new DropDownSelectionTextField<>(registerModel, minDelay);
registerEntryTextField.setBorder(null);
// this allows us to show the matching list when there is no text in the editor
@@ -230,7 +235,19 @@ class VarnodeLocationCellEditor extends AbstractCellEditor
}
}
Collections.sort(registers);
Collections.sort(registers, new RegisterComparator());
return registers;
}
private static class RegisterComparator implements Comparator<Register> {
private AlphaNumericComparator alphaNumericComparator = new AlphaNumericComparator();
@Override
public int compare(Register r1, Register r2) {
String s1 = r1.getName().toLowerCase();
String s2 = r2.getName().toLowerCase();
return alphaNumericComparator.compare(s1, s2);
}
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -28,6 +28,7 @@ import docking.widgets.tree.GTreeNode;
import generic.theme.GIcon;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.AlphaNumericComparator;
import resources.Icons;
public class RegisterTree extends GTree {
@@ -81,9 +82,8 @@ public class RegisterTree extends GTree {
list.add(reg);
}
}
Collections.sort(list);
Register[] registers = new Register[list.size()];
return list.toArray(registers);
return list.toArray(Register[]::new);
}
void setFiltered(boolean b) {
@@ -111,13 +111,6 @@ public class RegisterTree extends GTree {
Register[] registers = program.getProgramContext().getRegistersWithValues();
root.setRegisters(registers);
selectRegister(currentRegister);
// TODO: old school - delete me?
// runWhenTreeIsDone(new Runnable() {
// public void run() {
// selectRegister(currentRegister);
// }
// });
}
}
@@ -133,181 +126,196 @@ public class RegisterTree extends GTree {
}
return null;
}
}
abstract class SearchableRegisterTreeNode extends GTreeNode {
public GTreeNode findNode(Register register) {
List<GTreeNode> allChildren = getChildren();
for (GTreeNode child : allChildren) {
if (!(child instanceof RegisterTreeNode)) {
continue;
}
RegisterTreeNode node = (RegisterTreeNode) child;
if (node.getRegister().equals(register)) {
return node;
}
private static class RegisterNodeComparator implements Comparator<GTreeNode> {
GTreeNode foundNode = ((SearchableRegisterTreeNode) child).findNode(register);
if (foundNode != null) {
return foundNode;
private AlphaNumericComparator alphaNumericComparator =
new AlphaNumericComparator();
@Override
public int compare(GTreeNode n1, GTreeNode n2) {
String s1 = n1.getName().toLowerCase();
String s2 = n2.getName().toLowerCase();
return alphaNumericComparator.compare(s1, s2);
}
}
private static abstract class SearchableRegisterTreeNode extends GTreeNode {
private RegisterNodeComparator comparator = new RegisterNodeComparator();
void sortChildren() {
List<GTreeNode> children = new ArrayList<>(children());
Collections.sort(children, comparator);
setChildren(children);
for (GTreeNode child : children) {
SearchableRegisterTreeNode regNode = (SearchableRegisterTreeNode) child;
regNode.sortChildren();
}
}
return null;
}
}
class RegisterTreeRootNode extends SearchableRegisterTreeNode {
private Register[] lastRegisters;
@Override
public Icon getIcon(boolean expanded) {
return null;
}
@Override
public String getName() {
return "Registers";
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
public void setRegisters(Register[] registers) {
if (registers == lastRegisters) {
return;
}
removeAll(); // remove all current children before repopulating
lastRegisters = registers;
HashMap<String, RegisterTreeGroupNode> groups =
new HashMap<>();
List<GTreeNode> nodes = new ArrayList<>();
for (Register register : registers) {
if (register.getBaseRegister() != register &&
!register.getParentRegister().isHidden()) {
continue;
}
String groupName = register.getGroup();
if (groupName != null) {
RegisterTreeGroupNode group = groups.get(groupName);
if (group == null) {
group = new RegisterTreeGroupNode(groupName);
groups.put(groupName, group);
nodes.add(group);
public GTreeNode findNode(Register register) {
List<GTreeNode> allChildren = getChildren();
for (GTreeNode child : allChildren) {
if (!(child instanceof RegisterTreeNode)) {
continue;
}
RegisterTreeNode node = (RegisterTreeNode) child;
if (node.getRegister().equals(register)) {
return node;
}
GTreeNode foundNode = ((SearchableRegisterTreeNode) child).findNode(register);
if (foundNode != null) {
return foundNode;
}
group.addRegister(register);
}
else {
nodes.add(new RegisterTreeNode(register));
return null;
}
}
private static class RegisterTreeNode extends SearchableRegisterTreeNode {
private static Icon REG_ICON = new GIcon("icon.plugin.register");
private static Icon REG_GROUP_ICON = new GIcon("icon.plugin.register.provider");
private final Register register;
public RegisterTreeNode(Register register) {
this.register = register;
for (Register childRegister : register.getChildRegisters()) {
addNode(new RegisterTreeNode(childRegister));
}
}
Collections.sort(nodes);
setChildren(nodes);
}
}
class RegisterTreeNode extends SearchableRegisterTreeNode {
private static Icon REG_ICON = new GIcon("icon.plugin.register");
private static Icon REG_GROUP_ICON = new GIcon("icon.plugin.register.provider");
private final Register register;
public RegisterTreeNode(Register register) {
this.register = register;
for (Register childRegister : register.getChildRegisters()) {
addNode(new RegisterTreeNode(childRegister));
}
}
@Override
public Icon getIcon(boolean expanded) {
return register.hasChildren() ? REG_GROUP_ICON : REG_ICON;
}
@Override
public String getName() {
return register.getName() + " (" + register.getBitLength() + getAliases() + ")";
}
private String getAliases() {
StringBuffer buf = new StringBuffer();
for (String alias : register.getAliases()) {
buf.append(buf.length() == 0 ? "; " : ", ");
buf.append(alias);
}
return buf.toString();
}
@Override
public String getToolTip() {
return register.getDescription();
}
@Override
public boolean isLeaf() {
return !register.hasChildren();
}
@Override
public int compareTo(GTreeNode other) {
if (!(other instanceof RegisterTreeNode)) {
return 1;
}
return getName().compareTo(other.getName());
}
public Register getRegister() {
return register;
}
}
class RegisterTreeGroupNode extends SearchableRegisterTreeNode {
private static Icon OPEN_ICON = Icons.OPEN_FOLDER_ICON;
private static Icon CLOSED_ICON = Icons.CLOSED_FOLDER_ICON;
private String name;
public RegisterTreeGroupNode(String name) {
this.name = name;
}
@Override
public Icon getIcon(boolean expanded) {
return expanded ? OPEN_ICON : CLOSED_ICON;
}
@Override
public String getName() {
return name;
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
public void addRegister(Register register) {
addNode(new RegisterTreeNode(register));
}
@Override
public int compareTo(GTreeNode o) {
if (!(o instanceof RegisterTreeGroupNode)) {
return -1;
}
return name.compareTo(o.getName());
@Override
public Icon getIcon(boolean expanded) {
return register.hasChildren() ? REG_GROUP_ICON : REG_ICON;
}
@Override
public String getName() {
return register.getName() + " (" + register.getBitLength() + getAliases() +
")";
}
private String getAliases() {
StringBuffer buf = new StringBuffer();
for (String alias : register.getAliases()) {
buf.append(buf.length() == 0 ? "; " : ", ");
buf.append(alias);
}
return buf.toString();
}
@Override
public String getToolTip() {
return register.getDescription();
}
@Override
public boolean isLeaf() {
return !register.hasChildren();
}
Register getRegister() {
return register;
}
}
private static class RegisterTreeGroupNode extends SearchableRegisterTreeNode {
private static Icon OPEN_ICON = Icons.OPEN_FOLDER_ICON;
private static Icon CLOSED_ICON = Icons.CLOSED_FOLDER_ICON;
private String name;
public RegisterTreeGroupNode(String name) {
this.name = name;
}
@Override
public Icon getIcon(boolean expanded) {
return expanded ? OPEN_ICON : CLOSED_ICON;
}
@Override
public String getName() {
return name;
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
public void addRegister(Register register) {
addNode(new RegisterTreeNode(register));
}
}
private static class RegisterTreeRootNode extends SearchableRegisterTreeNode {
private Register[] lastRegisters;
@Override
public Icon getIcon(boolean expanded) {
return null;
}
@Override
public String getName() {
return "Registers";
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
public void setRegisters(Register[] registers) {
if (registers == lastRegisters) {
return;
}
removeAll(); // remove all current children before re-populating
lastRegisters = registers;
HashMap<String, RegisterTreeGroupNode> groups = new HashMap<>();
List<GTreeNode> nodes = new ArrayList<>();
for (Register register : registers) {
if (register.getBaseRegister() != register &&
!register.getParentRegister().isHidden()) {
continue;
}
String groupName = register.getGroup();
if (groupName != null) {
RegisterTreeGroupNode group = groups.get(groupName);
if (group == null) {
group = new RegisterTreeGroupNode(groupName);
groups.put(groupName, group);
nodes.add(group);
}
group.addRegister(register);
}
else {
nodes.add(new RegisterTreeNode(register));
}
}
setChildren(nodes);
sortChildren();
}
}
}

View File

@@ -15,15 +15,18 @@
*/
package ghidra.app.plugin.core.function.editor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.ProgramContext;
public class VarnodeLocationCellEditorTest extends AbstractGenericTest {
private static final int AARCH64_GENERAL_REGISTER_MAX = 30;
@@ -31,24 +34,23 @@ public class VarnodeLocationCellEditorTest extends AbstractGenericTest {
@Test
public void testAarch64XRegistersUseNumericOrder() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestProgram", ProgramBuilder._AARCH64);
try {
List<Register> registers = VarnodeLocationCellEditor.getSortedVisibleRegisters(
builder.getProgram().getProgramContext());
List<String> xRegisters = new ArrayList<>();
for (Register register : registers) {
if (register.getName().matches("x\\d+")) {
xRegisters.add(register.getName());
}
}
ProgramDB p = builder.getProgram();
builder.dispose();
List<String> expected = new ArrayList<>();
for (int i = 0; i <= AARCH64_GENERAL_REGISTER_MAX; i++) {
expected.add("x" + i);
ProgramContext context = p.getProgramContext();
List<Register> registers = VarnodeLocationCellEditor.getSortedVisibleRegisters(context);
List<String> xRegisters = new ArrayList<>();
for (Register register : registers) {
if (register.getName().matches("x\\d+")) {
xRegisters.add(register.getName());
}
assertEquals(expected, xRegisters);
}
finally {
builder.dispose();
List<String> expected = new ArrayList<>();
for (int i = 0; i <= AARCH64_GENERAL_REGISTER_MAX; i++) {
expected.add("x" + i);
}
assertEquals(expected, xRegisters);
}
}