mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-28 18:26:17 +08:00
Merge remote-tracking branch
'origin/GP-6326-dragonmacher-symbol-tree-convert-ns-bug--SQUASHED' (Closes #8869)
This commit is contained in:
+17
-15
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -71,20 +71,22 @@ public class ConvertToClassAction extends SymbolTreeContextAction {
|
|||||||
|
|
||||||
Symbol symbol = ((SymbolNode) node).getSymbol();
|
Symbol symbol = ((SymbolNode) node).getSymbol();
|
||||||
Namespace namespace = (Namespace) symbol.getObject();
|
Namespace namespace = (Namespace) symbol.getObject();
|
||||||
if (namespace != null) {
|
if (namespace == null) {
|
||||||
String name = namespace.getName();
|
return;
|
||||||
convertToClass(program, namespace);
|
|
||||||
program.flushEvents();
|
|
||||||
|
|
||||||
GTreeNode classesNode = root.getChild(SymbolCategory.CLASS_CATEGORY.getName());
|
|
||||||
if (classesNode != null) {
|
|
||||||
context.getSymbolTree().startEditing(classesNode, name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Msg.showInfo(this, null, "Classes Filtered Out of View",
|
|
||||||
"New class node is filtered out of view");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String name = namespace.getName();
|
||||||
|
convertToClass(program, namespace);
|
||||||
|
program.flushEvents();
|
||||||
|
|
||||||
|
GTreeNode classesNode = root.getChild(SymbolCategory.CLASS_CATEGORY.getName());
|
||||||
|
if (classesNode == null) {
|
||||||
|
Msg.showInfo(this, null, "Classes Filtered Out of View",
|
||||||
|
"New class node is filtered out of view");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.startEditing(classesNode, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void convertToClass(Program program, Namespace ns) {
|
private static void convertToClass(Program program, Namespace ns) {
|
||||||
|
|||||||
+23
-23
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -64,19 +64,21 @@ public class CreateClassAction extends SymbolTreeContextAction {
|
|||||||
if (object instanceof ClassCategoryNode) {
|
if (object instanceof ClassCategoryNode) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (object instanceof SymbolNode) {
|
if (!(object instanceof SymbolNode symbolNode)) {
|
||||||
SymbolNode symbolNode = (SymbolNode) object;
|
return false;
|
||||||
Symbol symbol = symbolNode.getSymbol();
|
|
||||||
SymbolType symbolType = symbol.getSymbolType();
|
|
||||||
if (symbolType == SymbolType.NAMESPACE) {
|
|
||||||
// allow SymbolType to perform additional checks
|
|
||||||
Namespace parentNamespace = (Namespace) symbol.getObject();
|
|
||||||
return SymbolType.CLASS.isValidParent(context.getProgram(), parentNamespace,
|
|
||||||
Address.NO_ADDRESS, parentNamespace.isExternal());
|
|
||||||
}
|
|
||||||
return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY);
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
Symbol symbol = symbolNode.getSymbol();
|
||||||
|
SymbolType symbolType = symbol.getSymbolType();
|
||||||
|
if (symbolType == SymbolType.NAMESPACE) {
|
||||||
|
Namespace parent = (Namespace) symbol.getObject();
|
||||||
|
if (parent == null) {
|
||||||
|
return false; // the symbol has been deleted, but the tree has not updated
|
||||||
|
}
|
||||||
|
return SymbolType.CLASS.isValidParent(context.getProgram(), parent,
|
||||||
|
Address.NO_ADDRESS, parent.isExternal());
|
||||||
|
}
|
||||||
|
return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNewClass(SymbolTreeActionContext context) {
|
private void createNewClass(SymbolTreeActionContext context) {
|
||||||
@@ -85,7 +87,6 @@ public class CreateClassAction extends SymbolTreeContextAction {
|
|||||||
Program program = context.getProgram();
|
Program program = context.getProgram();
|
||||||
Namespace parent = program.getGlobalNamespace();
|
Namespace parent = program.getGlobalNamespace();
|
||||||
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
|
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
|
||||||
|
|
||||||
if (node instanceof SymbolNode) {
|
if (node instanceof SymbolNode) {
|
||||||
Symbol symbol = ((SymbolNode) node).getSymbol();
|
Symbol symbol = ((SymbolNode) node).getSymbol();
|
||||||
parent = (Namespace) symbol.getObject();
|
parent = (Namespace) symbol.getObject();
|
||||||
@@ -99,14 +100,16 @@ public class CreateClassAction extends SymbolTreeContextAction {
|
|||||||
// error occurred
|
// error occurred
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
program.flushEvents();
|
program.flushEvents();
|
||||||
context.getSymbolTree().startEditing(node, newClassName);
|
context.getSymbolTree().startEditing(node, newClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createClass(Program program, Namespace parent) {
|
private String createClass(Program program, Namespace parent) {
|
||||||
String newClassName = "NewClass";
|
|
||||||
int transactionID = program.startTransaction("Create Class");
|
return program.withTransaction("Create Class", () -> {
|
||||||
try {
|
|
||||||
|
String newClassName = "NewClass";
|
||||||
SymbolTable symbolTable = program.getSymbolTable();
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
int oneUp = 0;
|
int oneUp = 0;
|
||||||
Namespace namespace = null;
|
Namespace namespace = null;
|
||||||
@@ -123,11 +126,8 @@ public class CreateClassAction extends SymbolTreeContextAction {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally {
|
|
||||||
program.endTransaction(transactionID, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newClassName;
|
return newClassName;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -146,17 +146,18 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
||||||
Namespace parentNs = symbol.getParentNamespace();
|
|
||||||
if (parentNs == globalNamespace) {
|
// See if the class lives on this Classes node. This can happen when the class is the child
|
||||||
// no need to search for the class in the tree; the class only lives at the top
|
// of a non-class namespace.
|
||||||
GTreeNode symbolNode = findNode(this, key, false, monitor);
|
GTreeNode symbolNode = findNode(this, key, false, monitor);
|
||||||
if (symbolNode != null) {
|
if (symbolNode != null) {
|
||||||
removeNode(symbolNode);
|
removeNode(symbolNode);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set getAllClassNodes() for a description of the map
|
// We could not find the node. See if it is under another class node.
|
||||||
|
// (See getAllClassNodes() for a description of the map.)
|
||||||
|
Namespace parentNs = symbol.getParentNamespace();
|
||||||
Map<GTreeNode, List<Namespace>> classNodes = getAllClassNodes(symbol, parentNs, monitor);
|
Map<GTreeNode, List<Namespace>> classNodes = getAllClassNodes(symbol, parentNs, monitor);
|
||||||
removeSymbol(key, classNodes, monitor);
|
removeSymbol(key, classNodes, monitor);
|
||||||
}
|
}
|
||||||
@@ -175,12 +176,12 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
|||||||
// parent for the given symbol
|
// parent for the given symbol
|
||||||
GTreeNode classNode = entry.getKey();
|
GTreeNode classNode = entry.getKey();
|
||||||
List<Namespace> parentPath = entry.getValue();
|
List<Namespace> parentPath = entry.getValue();
|
||||||
GTreeNode symbolParent =
|
GTreeNode symbolParent = getNamespaceNode(classNode, parentPath, false, monitor);
|
||||||
getNamespaceNode(classNode, parentPath, false, monitor);
|
if (symbolParent == null) {
|
||||||
GTreeNode symbolNode = findNode(symbolParent, key, false, monitor);
|
continue;
|
||||||
if (symbolParent != null) {
|
|
||||||
symbolParent.removeNode(symbolNode);
|
|
||||||
}
|
}
|
||||||
|
GTreeNode symbolNode = findNode(symbolParent, key, false, monitor);
|
||||||
|
symbolParent.removeNode(symbolNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+51
-1
@@ -316,6 +316,55 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
waitForCondition(tree::isEditing);
|
waitForCondition(tree::isEditing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassNestedUnderNonClassNamespace_RenameClass() throws Exception {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
The Classes folder flattens classes so every class appears at the top level. Because
|
||||||
|
users can expand classes, top level classes may also appear nested under other classes.
|
||||||
|
|
||||||
|
Classes
|
||||||
|
Class1
|
||||||
|
|
||||||
|
Namespaces
|
||||||
|
FooNs
|
||||||
|
Class1
|
||||||
|
*/
|
||||||
|
|
||||||
|
Namespace fooNs = createNamespace("FooNs");
|
||||||
|
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||||
|
|
||||||
|
expandClasses();
|
||||||
|
expandNamesapces();
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
renameSymbol(class1.getSymbol(), "Class1.renamed");
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1.renamed"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1.renamed"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClassCategoryDuplicates_NestedClass_RenameLabel() throws Exception {
|
public void testClassCategoryDuplicates_NestedClass_RenameLabel() throws Exception {
|
||||||
|
|
||||||
@@ -733,7 +782,8 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
convertToClassAction = getAction(plugin, "Convert to Class");
|
convertToClassAction = getAction(plugin, "Convert to Class");
|
||||||
assertNotNull(convertToClassAction);
|
assertNotNull(convertToClassAction);
|
||||||
|
|
||||||
navigateIncomingAction = (ToggleDockingAction) getAction(plugin, NavigateOnIncomingAction.NAME);
|
navigateIncomingAction =
|
||||||
|
(ToggleDockingAction) getAction(plugin, NavigateOnIncomingAction.NAME);
|
||||||
assertNotNull(navigateIncomingAction);
|
assertNotNull(navigateIncomingAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ public class GTreeModel implements TreeModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void valueForPathChanged(TreePath path, Object newValue) {
|
public void valueForPathChanged(TreePath path, Object newValue) {
|
||||||
|
if (path == null) {
|
||||||
|
// this can happen when we try to edit a node and it doesn't work due to filtering
|
||||||
|
return;
|
||||||
|
}
|
||||||
GTreeNode node = (GTreeNode) path.getLastPathComponent();
|
GTreeNode node = (GTreeNode) path.getLastPathComponent();
|
||||||
node.valueChanged(newValue);
|
node.valueChanged(newValue);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-6
@@ -31,12 +31,12 @@ import ghidra.util.task.TaskMonitor;
|
|||||||
public class GTreeStartEditingTask extends GTreeTask {
|
public class GTreeStartEditingTask extends GTreeTask {
|
||||||
|
|
||||||
private final GTreeNode modelParent;
|
private final GTreeNode modelParent;
|
||||||
private final GTreeNode editNode;
|
private final GTreeNode modelEditNode;
|
||||||
|
|
||||||
public GTreeStartEditingTask(GTree gTree, JTree jTree, GTreeNode editNode) {
|
public GTreeStartEditingTask(GTree gTree, JTree jTree, GTreeNode editNode) {
|
||||||
super(gTree);
|
super(gTree);
|
||||||
this.modelParent = tree.getModelNode(editNode.getParent());
|
this.modelParent = tree.getModelNode(editNode.getParent());
|
||||||
this.editNode = editNode;
|
this.modelEditNode = tree.getModelNode(editNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -55,13 +55,16 @@ public class GTreeStartEditingTask extends GTreeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void edit() {
|
private void edit() {
|
||||||
TreePath path = editNode.getTreePath();
|
|
||||||
|
GTreeNode viewEditNode = tree.getViewNode(modelEditNode);
|
||||||
|
|
||||||
|
TreePath path = viewEditNode.getTreePath();
|
||||||
CellEditor cellEditor = tree.getCellEditor();
|
CellEditor cellEditor = tree.getCellEditor();
|
||||||
cellEditor.addCellEditorListener(new CellEditorListener() {
|
cellEditor.addCellEditorListener(new CellEditorListener() {
|
||||||
@Override
|
@Override
|
||||||
public void editingCanceled(ChangeEvent e) {
|
public void editingCanceled(ChangeEvent e) {
|
||||||
cellEditor.removeCellEditorListener(this);
|
cellEditor.removeCellEditorListener(this);
|
||||||
tree.setSelectedNode(editNode); // reselect the node on cancel
|
tree.setSelectedNode(viewEditNode); // reselect the node on cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,7 +74,7 @@ public class GTreeStartEditingTask extends GTreeTask {
|
|||||||
|
|
||||||
// NOTE: there may be cases where this node search fails to correctly
|
// NOTE: there may be cases where this node search fails to correctly
|
||||||
// identify the renamed node when name and node class is insufficient to match.
|
// identify the renamed node when name and node class is insufficient to match.
|
||||||
Class<?> nodeClass = editNode.getClass();
|
Class<?> nodeClass = viewEditNode.getClass();
|
||||||
Predicate<GTreeNode> nodeMatches = n -> {
|
Predicate<GTreeNode> nodeMatches = n -> {
|
||||||
return nodeClass == n.getClass() && n.getName().equals(newName);
|
return nodeClass == n.getClass() && n.getName().equals(newName);
|
||||||
};
|
};
|
||||||
@@ -82,7 +85,7 @@ public class GTreeStartEditingTask extends GTreeTask {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tree.setNodeEditable(editNode);
|
tree.setNodeEditable(viewEditNode);
|
||||||
jTree.startEditingAtPath(path);
|
jTree.startEditingAtPath(path);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user