GP-6790 - Function Signature Editor - Added support for changing the namespace

This commit is contained in:
dragonmacher
2026-05-07 15:57:58 -04:00
parent 552850d2ae
commit dd4fddda96
20 changed files with 601 additions and 75 deletions
@@ -327,6 +327,10 @@
<P>This text field can be used to change the name of the function.</P>
<H3>Namespace</H3>
<P>This combo box and browse button can be use to change the function namespace.</P>
<H3>Calling Convention</H3>
<P>This field is a combobox that allows you to choose a calling convention from the list of
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 28 KiB

@@ -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.
@@ -21,6 +21,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.InvalidInputException;
@@ -40,19 +41,18 @@ class FunctionData extends FunctionDataView {
boolean checkStorage = false;
if (canCustomizeStorage()) {
// if (!originalFunctionData.canCustomizeStorage()) {
// // switched to using custom storage
// return true;
// }
checkStorage = true;
}
if (!returnInfo.getFormalDataType()
.equals(originalFunctionData.returnInfo.getFormalDataType())) {
DataType returnType = returnInfo.getFormalDataType();
DataType originalReturnType = originalFunctionData.returnInfo.getFormalDataType();
if (!returnType.equals(originalReturnType)) {
return true;
}
if (checkStorage &&
!returnInfo.getStorage().equals(originalFunctionData.returnInfo.getStorage())) {
VariableStorage returnStorage = returnInfo.getStorage();
VariableStorage originalReturnStorage = originalFunctionData.returnInfo.getStorage();
if (checkStorage && !returnStorage.equals(originalReturnStorage)) {
return true;
}
@@ -162,6 +162,10 @@ class FunctionData extends FunctionDataView {
this.name = n;
}
void setNamespace(Namespace ns) {
this.namespace = ns;
}
void setInline(boolean enable) {
this.isInLine = enable;
}
@@ -20,6 +20,7 @@ import java.util.*;
import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SymbolUtilities;
/**
@@ -30,6 +31,7 @@ class FunctionDataView {
Function function;
Namespace namespace;
String name;
boolean hasVarArgs;
ParamInfo returnInfo;
@@ -47,7 +49,8 @@ class FunctionDataView {
*/
FunctionDataView(Function function) {
this.function = function;
this.name = function.getName();
name = function.getName();
namespace = function.getParentNamespace();
allowCustomStorage = function.hasCustomVariableStorage();
hasVarArgs = function.hasVarArgs();
isInLine = function.isInline();
@@ -63,6 +66,7 @@ class FunctionDataView {
*/
FunctionDataView(FunctionDataView otherFunctionData) {
name = otherFunctionData.name;
namespace = otherFunctionData.namespace;
hasVarArgs = otherFunctionData.hasVarArgs;
returnInfo = otherFunctionData.returnInfo.copy();
for (ParamInfo p : otherFunctionData.parameters) {
@@ -82,6 +86,7 @@ class FunctionDataView {
return false;
}
if (!Objects.equals(name, otherFunctionData.name) ||
!Objects.equals(namespace, otherFunctionData.namespace) ||
!Objects.equals(callingConventionName, otherFunctionData.callingConventionName) ||
hasVarArgs != otherFunctionData.hasVarArgs ||
parameters.size() != otherFunctionData.parameters.size() ||
@@ -174,7 +179,7 @@ class FunctionDataView {
return buf.toString();
}
public Program getProgram() {
Program getProgram() {
return function.getProgram();
}
@@ -190,10 +195,14 @@ class FunctionDataView {
return parameters.size();
}
public String getName() {
String getName() {
return name;
}
Namespace getNamespace() {
return namespace;
}
String getNameString() {
return name.length() == 0 ? SymbolUtilities.getDefaultFunctionName(function.getEntryPoint())
: name;
@@ -17,8 +17,7 @@ package ghidra.app.plugin.core.function.editor;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.EventObject;
import java.util.*;
import java.util.List;
import javax.swing.*;
@@ -33,25 +32,29 @@ import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.widgets.OptionDialog;
import docking.widgets.button.BrowseButton;
import docking.widgets.button.GButton;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import docking.widgets.table.*;
import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors;
import generic.util.WindowUtilities;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.ToolTipUtils;
import ghidra.app.util.*;
import ghidra.app.util.cparser.C.CParserUtils;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.app.util.viewer.field.ListingColors.FunctionColors;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.symbol.ExternalLocation;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
import resources.Icons;
@@ -68,6 +71,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
private GTable parameterTable;
private JTextField nameField;
private GhidraComboBox<NamespaceWrapper> namespaceChoices;
private JCheckBox varArgsCheckBox;
private DataTypeManagerService service;
private JCheckBox inLineCheckBox;
@@ -167,7 +171,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
protected void okCallback() {
if (model.isInParsingMode()) {
try {
model.parseSignatureFieldText();
doParse();
}
catch (Exception e) {
handleParseException(e);
@@ -212,6 +216,32 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
super.close();
}
FunctionSignatureTextField getSignatureField() {
return signatureTextField;
}
void triggerSignatureParsing() {
try {
doParse();
}
catch (Exception ex) {
if (!handleParseException(ex)) {
return;
}
}
}
private void doParse() throws CancelledException, ParseException {
model.parseSignatureFieldText();
Namespace ns = model.getNamespace();
rebuildNamespaces(ns);
}
Namespace getSelectedNamesapce() {
return namespaceChoices.getSelectedItem().getNamespace();
}
private JComponent buildMainPanel(boolean hasOptionalSignatureCommit) {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
@@ -224,7 +254,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
private JComponent buildCenterPanel(boolean hasOptionalSignatureCommit) {
centerPanel = new JPanel(new BorderLayout());
centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
centerPanel.add(buildAttributePanel(), BorderLayout.NORTH);
centerPanel.add(createAttributePanel(), BorderLayout.NORTH);
centerPanel.add(buildTable(), BorderLayout.CENTER);
centerPanel.add(buildBottomPanel(hasOptionalSignatureCommit), BorderLayout.SOUTH);
centerPanel.getAccessibleContext().setAccessibleName("Function Attributes");
@@ -342,7 +372,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
signatureTextField.setActionListener(e -> {
try {
if (model.isInParsingMode()) {
model.parseSignatureFieldText();
doParse();
return;
}
}
@@ -361,7 +391,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
ActionListener tabListener = e -> {
try {
model.parseSignatureFieldText();
doParse();
}
catch (Exception ex) {
if (!handleParseException(ex)) {
@@ -411,16 +441,21 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
return result == OptionDialog.OPTION_TWO; // Option 2 is to abort
}
private Component buildAttributePanel() {
private Component createAttributePanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 15, 15));
JPanel leftPanel = new JPanel(new PairLayout(4, 8));
leftPanel.add(new GLabel("Function Name:"));
leftPanel.add(createNameField());
leftPanel.add(new GLabel("Calling Convention"));
leftPanel.add(new GLabel("Namespace:"));
leftPanel.add(createNamespacePanel());
leftPanel.add(new GLabel("Calling Convention:"));
leftPanel.add(createCallingConventionCombo());
leftPanel.setBorder(BorderFactory.createEmptyBorder(14, 0, 0, 10));
leftPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 10));
leftPanel.getAccessibleContext().setAccessibleName("Function");
panel.add(leftPanel, BorderLayout.CENTER);
panel.add(buildTogglePanel(), BorderLayout.EAST);
@@ -428,6 +463,133 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
return panel;
}
private Component createNamespacePanel() {
namespaceChoices = new GhidraComboBox<>();
namespaceChoices.setName("NamespaceComboBox");
namespaceChoices.addItemListener(e -> {
NamespaceWrapper wrapper = (NamespaceWrapper) e.getItem();
Namespace ns = wrapper.getNamespace();
model.setNamespace(ns);
});
initNamespaces();
selectNamespace();
JPanel nsPanel = new JPanel();
nsPanel.setLayout(new BoxLayout(nsPanel, BoxLayout.LINE_AXIS));
Component browsePanel = createBrowseButton();
nsPanel.add(namespaceChoices);
nsPanel.add(Box.createHorizontalStrut(5));
nsPanel.add(browsePanel);
return nsPanel;
}
private void selectNamespace() {
Function function = model.getFunction();
Symbol symbol = function.getSymbol();
Namespace ns = symbol.getParentNamespace();
namespaceChoices.setSelectedItem(new NamespaceWrapper(ns));
}
private Component createBrowseButton() {
JButton browseButton = new BrowseButton();
browseButton.setToolTipText("Choose Namespace");
browseButton.addActionListener(e -> showNamespaceChooser());
return browseButton;
}
private void showNamespaceChooser() {
Function function = model.getFunction();
Program program = function.getProgram();
NamespaceChooserDialog dialog = new NamespaceChooserDialog();
Namespace namespace = dialog.getNamespace(program);
if (namespace != null) {
rebuildNamespaces(namespace);
return;
}
String nsText = dialog.getNamespaceText();
if (StringUtils.isBlank(nsText)) {
return;
}
Namespace newNamespace = createNamespace(nsText);
rebuildNamespaces(newNamespace);
}
private void rebuildNamespaces(Namespace namespace) {
if (namespace == null) {
return;
}
Function function = model.getFunction();
Program program = function.getProgram();
NamespaceCache.add(program, namespace);
initNamespaces();
namespaceChoices.setSelectedItem(new NamespaceWrapper(namespace));
}
private Namespace createNamespace(String nsText) {
Program p = model.getProgram();
return p.withTransaction("Create Namespace", () -> {
Namespace globalNs = p.getGlobalNamespace();
try {
return NamespaceUtils.createNamespaceHierarchy(nsText, globalNs, p,
SourceType.USER_DEFINED);
}
catch (InvalidInputException e) {
setStatusText("Invalid Namespace name: " + nsText);
return null;
}
});
}
private void initNamespaces() {
namespaceChoices.removeAllItems();
for (Namespace namespace : getSelectableNamespaces()) {
namespaceChoices.addItem(new NamespaceWrapper(namespace));
}
}
private Collection<Namespace> getSelectableNamespaces() {
SequencedSet<Namespace> namespaces = new LinkedHashSet<>();
addGlobalNamespace(namespaces);
addCurrentNamespace(namespaces);
addRecentNamespaces(namespaces);
return namespaces;
}
private void addRecentNamespaces(SequencedSet<Namespace> namespaces) {
Program program = model.getProgram();
List<Namespace> recentNamespaces = NamespaceCache.get(program);
if (recentNamespaces == null) {
return;
}
for (Namespace namespace : recentNamespaces) {
if (!namespaces.contains(namespace)) {
namespaces.add(namespace);
}
}
}
private void addGlobalNamespace(SequencedSet<Namespace> namespaces) {
Program program = model.getProgram();
Namespace globalNamespace = program.getGlobalNamespace();
if (!namespaces.contains(globalNamespace)) {
namespaces.add(globalNamespace);
}
}
private void addCurrentNamespace(SequencedSet<Namespace> namespaces) {
Function function = model.getFunction();
Namespace ns = function.getParentNamespace();
namespaces.add(ns);
}
private Component buildTogglePanel() {
JPanel panel = new JPanel(new PairLayout());
varArgsCheckBox = new GCheckBox("Varargs");
@@ -753,8 +915,9 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
}
if (!model.hasValidName()) {
signatureTextField.setError(model.getFunctionNameStartPosition(),
model.getNameString().length());
int pos = model.getFunctionNameStartPosition();
int len = model.getNameString().length();
signatureTextField.setError(pos, len);
}
if (caretPosition < preview.length()) {
signatureTextField.setCaretPosition(caretPosition);
@@ -771,6 +934,47 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
}
}
//=================================================================================================
// Inner Classes
//=================================================================================================
private class NamespaceWrapper {
private Namespace namespace;
NamespaceWrapper(Namespace namespace) {
this.namespace = namespace;
}
Namespace getNamespace() {
return namespace;
}
@Override
public String toString() {
return namespace.getName(true);
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object == null) {
return false;
}
if (object.getClass() == getClass()) {
NamespaceWrapper w = (NamespaceWrapper) object;
return namespace.equals(w.namespace);
}
return false;
}
@Override
public int hashCode() {
return namespace.hashCode();
}
}
private class ParameterDataTypeCellRenderer extends GTableCellRenderer {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@@ -1133,7 +1337,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
if (!processEvent(e)) {
try {
model.parseSignatureFieldText();
doParse();
}
catch (Exception ex) {
handleParseException(ex);
@@ -18,8 +18,11 @@ package ghidra.app.plugin.core.function.editor;
import java.util.*;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.app.util.parser.FunctionSignatureParser;
import ghidra.app.util.parser.FunctionSignatureParser.FsParseResult;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
@@ -61,7 +64,7 @@ public class FunctionEditorModel {
this.dataTypeManagerService = service;
this.function = function;
this.program = function.getProgram();
functionData = new FunctionData(function);
this.functionData = new FunctionData(function);
this.originalFunctionData = new FunctionDataView(functionData);
validate();
}
@@ -118,6 +121,7 @@ public class FunctionEditorModel {
"Signature transformed due to auto-params and/or forced-indirect storage change";
isSignatureTransformed = false; // one-shot message
}
isValid =
hasValidName() && hasValidReturnType() && hasValidReturnStorage() && hasValidParams();
hasSignificantParameterChanges = false;
@@ -256,7 +260,7 @@ public class FunctionEditorModel {
* {@link VariableUtilities#checkVariableConflict(List, Variable, VariableStorage, VariableConflictHandler)}
* @param conflicts parameters whose storage conflicts
* @return return false to indicate conflicts have not been resolved and additional checks
* should be disconctinued.
* should be discontinued.
* @see VariableConflictHandler
*/
private boolean handleConflicts(List<Variable> conflicts) {
@@ -405,6 +409,18 @@ public class FunctionEditorModel {
notifyDataChanged();
}
Namespace getNamespace() {
return functionData.getNamespace();
}
void setNamespace(Namespace ns) {
if (getNamespace().equals(ns)) {
return;
}
functionData.setNamespace(ns);
notifyDataChanged();
}
String getCallingConventionName() {
return functionData.getCallingConventionName();
}
@@ -742,9 +758,12 @@ public class FunctionEditorModel {
if (b == canUseCustomStorage()) {
return;
}
functionData.setUseCustomStorage(b);
isSignatureTransformed = !functionData.getFunctionSignatureText()
.equals(originalFunctionData.getFunctionSignatureText());
String signatureText = functionData.getFunctionSignatureText();
String originalSignatureText = originalFunctionData.getFunctionSignatureText();
isSignatureTransformed = !signatureText.equals(originalSignatureText);
notifyDataChanged();
}
@@ -779,6 +798,11 @@ public class FunctionEditorModel {
function.setName(name, SourceType.USER_DEFINED);
}
Namespace namespace = functionData.getNamespace();
if (!namespace.equals(function.getParentNamespace())) {
function.setParentNamespace(namespace);
}
boolean isInline = functionData.isInline();
if (function.isInline() != isInline) {
function.setInline(isInline);
@@ -905,7 +929,7 @@ public class FunctionEditorModel {
return dt1.getLength() == dt2.getLength();
}
public void setFunctionData(FunctionDefinitionDataType functionDefinition) {
public void setFunctionData(Namespace ns, FunctionDefinitionDataType functionDefinition) {
setName(functionDefinition.getName());
@@ -941,6 +965,8 @@ public class FunctionEditorModel {
functionData.updateParameterAndReturnStorage();
functionData.setNamespace(ns);
notifyDataChanged();
}
@@ -1010,7 +1036,9 @@ public class FunctionEditorModel {
void parseSignatureFieldText() throws ParseException, CancelledException {
FunctionSignatureParser parser =
new FunctionSignatureParser(program.getDataTypeManager(), dataTypeManagerService);
FunctionDefinitionDataType f = parser.parse(getFunctionSignature(), signatureFieldText);
FsParseResult result =
parser.parseWithNamespace(getFunctionSignature(), signatureFieldText);
FunctionDefinitionDataType f = result.functionDefinition();
// Preserve calling convention and noreturn flag from current model
f.setNoReturn(functionData.hasNoReturn());
@@ -1021,10 +1049,31 @@ public class FunctionEditorModel {
// ignore
}
setFunctionData(f);
Namespace ns = createNamespace(result.namespace());
setFunctionData(ns, f);
isInParsingMode = false;
}
private Namespace createNamespace(SymbolPath path) throws ParseException {
if (path == null) {
return null;
}
String nsText = path.getPath();
return program.withTransaction("Create Namespace", () -> {
Namespace globalNs = program.getGlobalNamespace();
try {
return NamespaceUtils.createNamespaceHierarchy(nsText, globalNs, program,
SourceType.USER_DEFINED);
}
catch (InvalidInputException e) {
throw new ParseException("Invalid Namespace name: " + nsText);
}
});
}
int getFunctionNameStartPosition() {
return getFormalReturnType().getName().length() + 1;
}
@@ -40,6 +40,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.VerticalLayout;
/**
@@ -575,8 +576,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
// the number of columns determines the default width of the add/edit label dialog
labelNameChoices.setColumns(20);
labelNameChoices.setName("label.name.choices");
GhidraComboBox<NamespaceWrapper> comboBox = new GhidraComboBox<>();
namespaceChoices = comboBox;
namespaceChoices = new GhidraComboBox<>();
primaryCheckBox = new GCheckBox("Primary");
primaryCheckBox.setMnemonic('P');
@@ -636,12 +636,40 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
private void showNamespaceChooser() {
NamespaceChooserDialog dialog = new NamespaceChooserDialog();
Namespace namespace = dialog.getNameSpace(program);
Namespace namespace = dialog.getNamespace(program);
if (namespace != null) {
NamespaceCache.add(program, namespace);
initNamespaces();
namespaceChoices.setSelectedItem(new NamespaceWrapper(namespace));
}
String nsText = dialog.getNamespaceText();
if (StringUtils.isBlank(nsText)) {
return;
}
Namespace newNamespace = createNamespace(nsText);
if (newNamespace != null) {
NamespaceCache.add(program, newNamespace);
initNamespaces();
namespaceChoices.setSelectedItem(new NamespaceWrapper(newNamespace));
}
}
private Namespace createNamespace(String nsText) {
return program.withTransaction("Create Namespace", () -> {
Namespace globalNs = program.getGlobalNamespace();
try {
return NamespaceUtils.createNamespaceHierarchy(nsText, globalNs, program,
SourceType.USER_DEFINED);
}
catch (InvalidInputException e) {
setStatusText("Invalid Namespace name: " + nsText);
return null;
}
});
}
private void addListeners() {
@@ -23,6 +23,9 @@ import ghidra.util.datastruct.LRUSet;
/**
* Static class for remember the last few namespaces used for a program.
*
* <p>Note: This class is not currently multi-threaded. Accesses are expected to be on the Swing
* thread.
*/
public class NamespaceCache {
public static final int MAX_RECENTS = 10;
@@ -48,7 +48,11 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
addCancelButton();
}
public Namespace getNameSpace(Program program) {
public void setText(String text) {
dropDownField.setText(text);
}
public Namespace getNamespace(Program program) {
List<Namespace> namespaces = gatherNamespaces(program);
if (namespaces == null) {
// user cancelled while gathering namespaces
@@ -59,6 +63,10 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
return chosenNamespace;
}
public String getNamespaceText() {
return dropDownField.getText();
}
@Override
protected void okCallback() {
chosenNamespace = dropDownField.getSelectedValue();
@@ -68,6 +76,7 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
@Override
protected void cancelCallback() {
chosenNamespace = null;
dropDownField.setText("");
close();
}
@@ -82,6 +91,7 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
panel.add(new JLabel("Namespace: "));
dropDownField = new DropDownSelectionTextField<>(namespaceModel);
dropDownField.setShowMatchingListOnEmptyText(true);
panel.add(dropDownField);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
return panel;
@@ -21,6 +21,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.services.DataTypeQueryService;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.model.data.*;
@@ -56,7 +57,8 @@ public class FunctionSignatureParser {
private DataTypeParser dataTypeParser;
private Map<String, DataType> dtMap = new HashMap<>();
private Map<String, String> nameMap = new HashMap<>();
/** Stores parameter names that required name fixup for parsing to work correctly */
private Map<String, String> replacedNameMap = new HashMap<>();
private DataTypeManager destDataTypeManager;
private ParserDataTypeManagerService dtmService;
@@ -96,8 +98,16 @@ public class FunctionSignatureParser {
*/
public FunctionDefinitionDataType parse(FunctionSignature originalSignature,
String signatureText) throws ParseException, CancelledException {
FsParseResult result = parseWithNamespace(originalSignature, signatureText);
return result.functionDefinition();
}
public FsParseResult parseWithNamespace(FunctionSignature originalSignature,
String signatureText) throws ParseException, CancelledException {
dtMap.clear();
nameMap.clear();
replacedNameMap.clear();
if (dtmService != null) {
dtmService.clearCache(); // clear datatype selection cache
}
@@ -108,14 +118,18 @@ public class FunctionSignatureParser {
}
String functionName = extractFunctionName(signatureText);
SymbolPath path = new SymbolPath(functionName);
SymbolPath nsPath = path.getParent();
String name = path.getName();
FunctionDefinitionDataType function =
new FunctionDefinitionDataType(functionName, destDataTypeManager);
new FunctionDefinitionDataType(name, destDataTypeManager);
function.setReturnType(extractReturnType(signatureText));
function.setArguments(extractArguments(signatureText));
function.setVarArgs(hasVarArgs(signatureText));
return function;
return new FsParseResult(nsPath, function);
}
private void initDataTypeMap(FunctionSignature signature) {
@@ -247,7 +261,7 @@ public class FunctionSignatureParser {
if (canParseName(name)) {
return text;
}
nameMap.put(replacementName, name);
replacedNameMap.put(replacementName, name);
return substitute(text, name, replacementName);
}
@@ -299,8 +313,8 @@ public class FunctionSignatureParser {
}
private String resolveName(String name) throws ParseException {
if (nameMap.containsKey(name)) {
return nameMap.get(name);
if (replacedNameMap.containsKey(name)) {
return replacedNameMap.get(name);
}
if (!canParseName(name)) {
throw new ParseException("Can't parse name: " + name);
@@ -320,6 +334,14 @@ public class FunctionSignatureParser {
return !StringUtils.containsAny(text, "()<>,");
}
/**
* A simple object to hold data for the results of parsing the function signature text
* @param namespace the namespace; may be null
* @param functionDefinition the function definition; will not be null
*/
public record FsParseResult(SymbolPath namespace,
FunctionDefinitionDataType functionDefinition) {}
/**
* Provides a simple caching datatype manager service wrapper.<br>
* Implementation intended for use with {@link FunctionSignatureParser}
@@ -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.
@@ -15,34 +15,36 @@
*/
package ghidra.app.plugin.core.function.editor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import javax.swing.AbstractButton;
import javax.swing.ComboBoxModel;
import javax.swing.table.TableCellEditor;
import org.junit.*;
import docking.action.DockingActionIf;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.button.BrowseButton;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.table.GTable;
import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.*;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.test.*;
public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTest {
public FunctionEditorDialogTest() {
super();
}
private TestEnv env;
private PluginTool tool;
private AddressFactory addrFactory;
@@ -64,10 +66,11 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
@After
public void tearDown() {
closeAllWindows();
env.dispose();
}
/**
/*
* Tests that an invalid parameter type entry will generate the proper error message
* shown in the status box, and NOT present the user with a stack trace.
*/
@@ -93,10 +96,180 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
assertTrue(dialog.getStatusText().contains("Invalid data type"));
}
@Test
public void testSetNamespace() throws Exception {
createFunctionAtEntry();
String newNamespaceName = "NewNamespace";
Namespace newNs = createNamespace(newNamespaceName);
FunctionEditorDialog dialog = editFunction();
pickNamespaceFromComboBox(dialog, newNs);
pressButtonByText(dialog, "OK");
waitForBusyTool(tool);
Function f = getFunction("0x1006420");
Namespace actualNamespace = f.getParentNamespace();
assertEquals(newNs, actualNamespace);
}
@Test
public void testSetNamespace_Browse_CreateNew() throws Exception {
createFunctionAtEntry();
String newNamespace = "NonExistingNamespace";
FunctionEditorDialog dialog = editFunction();
setNamespaceUsingNsChooserDilaog(dialog, newNamespace);
pressButtonByText(dialog, "OK");
waitForBusyTool(tool);
Function f = getFunction("0x1006420");
Namespace actualNamespace = f.getParentNamespace();
assertEquals(newNamespace, actualNamespace.toString());
}
@Test
public void testSetNamespace_Browse_CreateNew_NamespacePath() throws Exception {
createFunctionAtEntry();
String newNamespacePath = "Foo::Bar::NonExistingNamespace";
FunctionEditorDialog dialog = editFunction();
setNamespaceUsingNsChooserDilaog(dialog, newNamespacePath);
pressButtonByText(dialog, "OK");
waitForBusyTool(tool);
Function f = getFunction("0x1006420");
Namespace actualNamespace = f.getParentNamespace();
SymbolPath expectedPath = new SymbolPath(newNamespacePath);
SymbolPath actualPath = new SymbolPath(actualNamespace.getPathList(true));
assertEquals(expectedPath, actualPath);
}
@Test
public void testSetNamespace_ViaTextEditor() throws Exception {
createFunctionAtEntry();
String newNamespacePath = "Foo::Bar";
FunctionEditorDialog dialog = editFunction();
setNamespaceUsingTextEditor(dialog, newNamespacePath);
assertNamespaceNotVisibleInEditorAfterParsing(dialog);
assertNamespaceComboBoxIsShowingNamespace(dialog, newNamespacePath);
pressButtonByText(dialog, "OK");
waitForBusyTool(tool);
Function f = getFunction("0x1006420");
Namespace actualNamespace = f.getParentNamespace();
SymbolPath expectedPath = new SymbolPath(newNamespacePath);
SymbolPath actualPath = new SymbolPath(actualNamespace.getPathList(true));
assertEquals(expectedPath, actualPath);
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void assertNamespaceComboBoxIsShowingNamespace(FunctionEditorDialog dialog,
String expectedNsPath) {
Namespace actualNs = runSwing(() -> dialog.getSelectedNamesapce());
assertEquals(expectedNsPath, actualNs.toString());
}
private void assertNamespaceNotVisibleInEditorAfterParsing(FunctionEditorDialog dialog) {
FunctionSignatureTextField field = dialog.getSignatureField();
String signature = runSwing(() -> field.getText());
assertFalse("Namespace should not be visible in the editor after parsing",
signature.contains("::"));
}
private void setNamespaceUsingTextEditor(FunctionEditorDialog dialog, String ns) {
FunctionSignatureTextField field = dialog.getSignatureField();
String signature = runSwing(() -> field.getText());
// Insert namespace in front of name. Format:
// undefined entry (void)
int paren = signature.indexOf('(');
int spaceBeforeParen = paren - 1;
int space = signature.lastIndexOf(' ', spaceBeforeParen - 1);
String beginning = signature.substring(0, space + 1);
String end = signature.substring(space + 1);
String updated = beginning + ns + "::" + end;
setText(field, updated);
runSwing(() -> dialog.triggerSignatureParsing());
}
private void pickNamespaceFromComboBox(FunctionEditorDialog dialog, Namespace ns) {
GhidraComboBox<?> combo =
(GhidraComboBox<?>) findComponentByName(dialog, "NamespaceComboBox");
int index = indexOf(combo, ns);
if (index < 0) {
fail("Could not find namespace in combo box: " + ns);
}
runSwing(() -> combo.setSelectedIndex(index));
}
private int indexOf(GhidraComboBox<?> combo, Namespace ns) {
return runSwing(() -> {
String nsName = ns.getName();
ComboBoxModel<?> model = combo.getModel();
int n = model.getSize();
for (int i = 0; i < n; i++) {
Object element = model.getElementAt(i);
String elementText = element.toString();
if (elementText.equals(nsName)) {
return i;
}
}
return -1;
});
}
private void setNamespaceUsingNsChooserDilaog(FunctionEditorDialog dialog,
String newNamespace) {
AbstractButton button = findButtonByName(dialog, BrowseButton.NAME);
pressButton(button, false);
NamespaceChooserDialog nsDialog = waitForDialogComponent(NamespaceChooserDialog.class);
runSwing(() -> nsDialog.setText(newNamespace));
pressButtonByText(nsDialog, "OK");
waitForBusyTool(tool);
}
private Namespace createNamespace(String newNamespace) {
Namespace newNs = tx(program, () -> {
return NamespaceUtils.createNamespaceHierarchy(newNamespace, null, program,
SourceType.USER_DEFINED);
});
runSwing(() -> NamespaceCache.add(program, newNs));
return newNs;
}
private void setEditorText(TableCellEditor cellEditor, String text) {
DropDownSelectionTextField<?> textField = getDataTypeEditor(cellEditor);
setText(textField, text);
@@ -106,7 +279,7 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
private FunctionEditorDialog editFunction() {
performAction(editFunction, cb.getProvider(), false);
return waitForDialogComponent(null, FunctionEditorDialog.class, DEFAULT_WINDOW_TIMEOUT);
return waitForDialogComponent(FunctionEditorDialog.class);
}
private void finishEditing(final TableCellEditor cellEditor) {
@@ -150,6 +323,11 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
addrFactory = program.getAddressFactory();
}
private Function getFunction(String addr) {
FunctionManager fm = program.getFunctionManager();
return fm.getFunctionAt(addr(addr));
}
private void createFunctionAtEntry() {
FunctionManager fm = program.getFunctionManager();
Function f = fm.getFunctionAt(addr("0x1006420"));
@@ -132,6 +132,7 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration
public void testExtractFunctionName() throws Exception {
assertEquals("bob", parser.extractFunctionName("void bob(int a)"));
assertEquals("bob", parser.extractFunctionName("void bob (int a)"));
assertEquals("Foo::Bar::bob", parser.extractFunctionName("void Foo::Bar::bob (int a)"));
}
@Test
@@ -22,7 +22,6 @@ import java.util.List;
import org.junit.*;
import generic.test.AbstractGuiTest;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
@@ -40,7 +39,6 @@ public class FunctionEditorModelTest extends AbstractGuiTest {
private volatile boolean dataChangeCalled;
private Structure bigStruct;
private ProgramDB program;
private DataTypeManagerService service;
private volatile boolean tableRowsChanged;
class MyModelChangeListener implements ModelChangeListener {
@@ -1612,9 +1610,9 @@ public class FunctionEditorModelTest extends AbstractGuiTest {
assertEquals("R9D:4", storage.toString());
model.setUseCustomizeStorage(false);
// no change to 'this', return ptr consumed and unfortunately
// no change to 'this', return pointer consumed and unfortunately
// injected before custom 'this' param
// TODO: should we be removing 'this' param if not __thiscall ?
// Note: should we be removing 'this' param if not __thiscall ?
assertTrue(model.getReturnType().isEquivalent(new PointerDataType(bigStruct)));
assertTrue(model.getFormalReturnType().isEquivalent(bigStruct));
@@ -1843,7 +1841,6 @@ public class FunctionEditorModelTest extends AbstractGuiTest {
model.setUseCustomizeStorage(true);
VariableStorage paramStorage1 = model.getParameters().get(0).getStorage();
VariableStorage paramStorage2 = model.getParameters().get(1).getStorage();
VariableStorage paramStorage3 = model.getParameters().get(2).getStorage();
model.setSignatureFieldText("int joe(int e, int c, int f, int g)");
@@ -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.
@@ -27,6 +27,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.HelpLocation;
import ghidra.util.UndefinedFunction;
@@ -99,7 +100,10 @@ public class SpecifyCPrototypeAction extends AbstractDecompilerAction {
if (useCustom) {
// Force custom storage
model.setUseCustomizeStorage(true);
model.setFunctionData(buildSignature(hf));
Function function = hf.getFunction();
Namespace ns = function.getParentNamespace();
FunctionDefinitionDataType signature = buildSignature(hf);
model.setFunctionData(ns, signature);
model.setReturnStorage(functionPrototype.getReturnStorage());
parameters = model.getParameters();
for (int i = 0; i < decompParamCnt; i++) {
@@ -169,7 +173,9 @@ public class SpecifyCPrototypeAction extends AbstractDecompilerAction {
if (function.getEntryPoint().equals(hf.getFunction().getEntryPoint())) {
if (function.getSignatureSource() == SourceType.DEFAULT) {
model.setFunctionData(buildSignature(hf));
Namespace ns = function.getParentNamespace();
FunctionDefinitionDataType signature = buildSignature(hf);
model.setFunctionData(ns, signature);
verifyDynamicEditorModel(hf, model);
}
else if (function.getReturnType() == DataType.DEFAULT) {
@@ -145,8 +145,8 @@ public class FcgLevel implements Comparable<FcgLevel> {
/**
* Returns the child level of this level. The child of a level has the same direction
* as this level, with a distance of one more than this level.
*
* @param the direction of the child
*
* @param newDirection the direction of the child
* @return returns the child level of this level
* @throws IllegalArgumentException if this is the source level, which is row 1
*/
@@ -38,7 +38,7 @@ import ghidra.util.Msg;
*
* <P>This class is handed a group of edges to processes. In this group there are vertices that
* do not need to be arranged, referred to as the {@code existing} vertices. This
* classes uses {@link VertexCollection} to find and store the new vertices that need
* classes uses {@link FcgExpandingVertexCollection} to find and store the new vertices that need
* to be arranged.
*/
public class BowTieExpandVerticesJob extends AbstractGraphTransitionJob<FcgVertex, FcgEdge> {
@@ -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.
@@ -31,7 +31,7 @@ import util.CollectionUtils;
* A container to house all newly added vertices (those being arranged) and the sources, or
* 'from' vertices, of the new vertices.
*
* <P>This offers exiting vertices and new vertices pre-sorted by position in the graph in
* <P>This offers existing vertices and new vertices pre-sorted by position in the graph in
* order to minimize edge crossings. Specifically, the new vertices will be sorted
* by the level of the parent and then the x-value of the parent so that the
* immediate parent level will be preferred, with the x-value dictating where to place
@@ -131,6 +131,12 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
}
}
@SuppressWarnings("unchecked")
@Override
public E getSelectedItem() {
return (E) super.getSelectedItem();
}
/**
* Returns the text in combobox's editor text component
* @return the text in combobox's editor text component
@@ -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.
@@ -129,4 +129,9 @@ public class GlobalNamespace implements Namespace {
return false;
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
@@ -79,7 +79,7 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator {
public void testChooseNamespace() {
runSwingLater(() -> {
NamespaceChooserDialog dialog = new NamespaceChooserDialog();
dialog.getNameSpace(program);
dialog.getNamespace(program);
});
waitForDialogComponent(NamespaceChooserDialog.class);
captureDialog();