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> <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> <H3>Calling Convention</H3>
<P>This field is a combobox that allows you to choose a calling convention from the list of <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"); * 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.
@@ -21,6 +21,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import ghidra.util.exception.InvalidInputException; import ghidra.util.exception.InvalidInputException;
@@ -40,19 +41,18 @@ class FunctionData extends FunctionDataView {
boolean checkStorage = false; boolean checkStorage = false;
if (canCustomizeStorage()) { if (canCustomizeStorage()) {
// if (!originalFunctionData.canCustomizeStorage()) {
// // switched to using custom storage
// return true;
// }
checkStorage = true; checkStorage = true;
} }
if (!returnInfo.getFormalDataType() DataType returnType = returnInfo.getFormalDataType();
.equals(originalFunctionData.returnInfo.getFormalDataType())) { DataType originalReturnType = originalFunctionData.returnInfo.getFormalDataType();
if (!returnType.equals(originalReturnType)) {
return true; 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; return true;
} }
@@ -162,6 +162,10 @@ class FunctionData extends FunctionDataView {
this.name = n; this.name = n;
} }
void setNamespace(Namespace ns) {
this.namespace = ns;
}
void setInline(boolean enable) { void setInline(boolean enable) {
this.isInLine = enable; this.isInLine = enable;
} }
@@ -20,6 +20,7 @@ import java.util.*;
import ghidra.program.model.data.VoidDataType; import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SymbolUtilities; import ghidra.program.model.symbol.SymbolUtilities;
/** /**
@@ -30,6 +31,7 @@ class FunctionDataView {
Function function; Function function;
Namespace namespace;
String name; String name;
boolean hasVarArgs; boolean hasVarArgs;
ParamInfo returnInfo; ParamInfo returnInfo;
@@ -47,7 +49,8 @@ class FunctionDataView {
*/ */
FunctionDataView(Function function) { FunctionDataView(Function function) {
this.function = function; this.function = function;
this.name = function.getName(); name = function.getName();
namespace = function.getParentNamespace();
allowCustomStorage = function.hasCustomVariableStorage(); allowCustomStorage = function.hasCustomVariableStorage();
hasVarArgs = function.hasVarArgs(); hasVarArgs = function.hasVarArgs();
isInLine = function.isInline(); isInLine = function.isInline();
@@ -63,6 +66,7 @@ class FunctionDataView {
*/ */
FunctionDataView(FunctionDataView otherFunctionData) { FunctionDataView(FunctionDataView otherFunctionData) {
name = otherFunctionData.name; name = otherFunctionData.name;
namespace = otherFunctionData.namespace;
hasVarArgs = otherFunctionData.hasVarArgs; hasVarArgs = otherFunctionData.hasVarArgs;
returnInfo = otherFunctionData.returnInfo.copy(); returnInfo = otherFunctionData.returnInfo.copy();
for (ParamInfo p : otherFunctionData.parameters) { for (ParamInfo p : otherFunctionData.parameters) {
@@ -82,6 +86,7 @@ class FunctionDataView {
return false; return false;
} }
if (!Objects.equals(name, otherFunctionData.name) || if (!Objects.equals(name, otherFunctionData.name) ||
!Objects.equals(namespace, otherFunctionData.namespace) ||
!Objects.equals(callingConventionName, otherFunctionData.callingConventionName) || !Objects.equals(callingConventionName, otherFunctionData.callingConventionName) ||
hasVarArgs != otherFunctionData.hasVarArgs || hasVarArgs != otherFunctionData.hasVarArgs ||
parameters.size() != otherFunctionData.parameters.size() || parameters.size() != otherFunctionData.parameters.size() ||
@@ -174,7 +179,7 @@ class FunctionDataView {
return buf.toString(); return buf.toString();
} }
public Program getProgram() { Program getProgram() {
return function.getProgram(); return function.getProgram();
} }
@@ -190,10 +195,14 @@ class FunctionDataView {
return parameters.size(); return parameters.size();
} }
public String getName() { String getName() {
return name; return name;
} }
Namespace getNamespace() {
return namespace;
}
String getNameString() { String getNameString() {
return name.length() == 0 ? SymbolUtilities.getDefaultFunctionName(function.getEntryPoint()) return name.length() == 0 ? SymbolUtilities.getDefaultFunctionName(function.getEntryPoint())
: name; : name;
@@ -17,8 +17,7 @@ package ghidra.app.plugin.core.function.editor;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.util.Arrays; import java.util.*;
import java.util.EventObject;
import java.util.List; import java.util.List;
import javax.swing.*; import javax.swing.*;
@@ -33,25 +32,29 @@ import org.apache.commons.lang3.StringUtils;
import docking.*; import docking.*;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.button.BrowseButton;
import docking.widgets.button.GButton; import docking.widgets.button.GButton;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import docking.widgets.table.*; import docking.widgets.table.*;
import generic.theme.GIcon; import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
import ghidra.app.services.DataTypeManagerService; 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.CParserUtils;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.app.util.viewer.field.ListingColors.FunctionColors; import ghidra.app.util.viewer.field.ListingColors.FunctionColors;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.program.model.data.VoidDataType; import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.*;
import ghidra.program.model.listing.VariableStorage; import ghidra.program.model.symbol.*;
import ghidra.program.model.symbol.ExternalLocation;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout; import ghidra.util.layout.VerticalLayout;
import resources.Icons; import resources.Icons;
@@ -68,6 +71,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
private GTable parameterTable; private GTable parameterTable;
private JTextField nameField; private JTextField nameField;
private GhidraComboBox<NamespaceWrapper> namespaceChoices;
private JCheckBox varArgsCheckBox; private JCheckBox varArgsCheckBox;
private DataTypeManagerService service; private DataTypeManagerService service;
private JCheckBox inLineCheckBox; private JCheckBox inLineCheckBox;
@@ -167,7 +171,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
protected void okCallback() { protected void okCallback() {
if (model.isInParsingMode()) { if (model.isInParsingMode()) {
try { try {
model.parseSignatureFieldText(); doParse();
} }
catch (Exception e) { catch (Exception e) {
handleParseException(e); handleParseException(e);
@@ -212,6 +216,32 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
super.close(); 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) { private JComponent buildMainPanel(boolean hasOptionalSignatureCommit) {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
@@ -224,7 +254,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
private JComponent buildCenterPanel(boolean hasOptionalSignatureCommit) { private JComponent buildCenterPanel(boolean hasOptionalSignatureCommit) {
centerPanel = new JPanel(new BorderLayout()); centerPanel = new JPanel(new BorderLayout());
centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); 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(buildTable(), BorderLayout.CENTER);
centerPanel.add(buildBottomPanel(hasOptionalSignatureCommit), BorderLayout.SOUTH); centerPanel.add(buildBottomPanel(hasOptionalSignatureCommit), BorderLayout.SOUTH);
centerPanel.getAccessibleContext().setAccessibleName("Function Attributes"); centerPanel.getAccessibleContext().setAccessibleName("Function Attributes");
@@ -342,7 +372,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
signatureTextField.setActionListener(e -> { signatureTextField.setActionListener(e -> {
try { try {
if (model.isInParsingMode()) { if (model.isInParsingMode()) {
model.parseSignatureFieldText(); doParse();
return; return;
} }
} }
@@ -361,7 +391,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
ActionListener tabListener = e -> { ActionListener tabListener = e -> {
try { try {
model.parseSignatureFieldText(); doParse();
} }
catch (Exception ex) { catch (Exception ex) {
if (!handleParseException(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 return result == OptionDialog.OPTION_TWO; // Option 2 is to abort
} }
private Component buildAttributePanel() { private Component createAttributePanel() {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 15, 15)); panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 15, 15));
JPanel leftPanel = new JPanel(new PairLayout(4, 8)); JPanel leftPanel = new JPanel(new PairLayout(4, 8));
leftPanel.add(new GLabel("Function Name:")); leftPanel.add(new GLabel("Function Name:"));
leftPanel.add(createNameField()); 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.add(createCallingConventionCombo());
leftPanel.setBorder(BorderFactory.createEmptyBorder(14, 0, 0, 10));
leftPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 10));
leftPanel.getAccessibleContext().setAccessibleName("Function"); leftPanel.getAccessibleContext().setAccessibleName("Function");
panel.add(leftPanel, BorderLayout.CENTER); panel.add(leftPanel, BorderLayout.CENTER);
panel.add(buildTogglePanel(), BorderLayout.EAST); panel.add(buildTogglePanel(), BorderLayout.EAST);
@@ -428,6 +463,133 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
return panel; 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() { private Component buildTogglePanel() {
JPanel panel = new JPanel(new PairLayout()); JPanel panel = new JPanel(new PairLayout());
varArgsCheckBox = new GCheckBox("Varargs"); varArgsCheckBox = new GCheckBox("Varargs");
@@ -753,8 +915,9 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
} }
if (!model.hasValidName()) { if (!model.hasValidName()) {
signatureTextField.setError(model.getFunctionNameStartPosition(), int pos = model.getFunctionNameStartPosition();
model.getNameString().length()); int len = model.getNameString().length();
signatureTextField.setError(pos, len);
} }
if (caretPosition < preview.length()) { if (caretPosition < preview.length()) {
signatureTextField.setCaretPosition(caretPosition); 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 { private class ParameterDataTypeCellRenderer extends GTableCellRenderer {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@@ -1133,7 +1337,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod
if (!processEvent(e)) { if (!processEvent(e)) {
try { try {
model.parseSignatureFieldText(); doParse();
} }
catch (Exception ex) { catch (Exception ex) {
handleParseException(ex); handleParseException(ex);
@@ -18,8 +18,11 @@ package ghidra.app.plugin.core.function.editor;
import java.util.*; import java.util.*;
import ghidra.app.services.DataTypeManagerService; 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.cparser.C.ParseException;
import ghidra.app.util.parser.FunctionSignatureParser; import ghidra.app.util.parser.FunctionSignatureParser;
import ghidra.app.util.parser.FunctionSignatureParser.FsParseResult;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
@@ -61,7 +64,7 @@ public class FunctionEditorModel {
this.dataTypeManagerService = service; this.dataTypeManagerService = service;
this.function = function; this.function = function;
this.program = function.getProgram(); this.program = function.getProgram();
functionData = new FunctionData(function); this.functionData = new FunctionData(function);
this.originalFunctionData = new FunctionDataView(functionData); this.originalFunctionData = new FunctionDataView(functionData);
validate(); validate();
} }
@@ -118,6 +121,7 @@ public class FunctionEditorModel {
"Signature transformed due to auto-params and/or forced-indirect storage change"; "Signature transformed due to auto-params and/or forced-indirect storage change";
isSignatureTransformed = false; // one-shot message isSignatureTransformed = false; // one-shot message
} }
isValid = isValid =
hasValidName() && hasValidReturnType() && hasValidReturnStorage() && hasValidParams(); hasValidName() && hasValidReturnType() && hasValidReturnStorage() && hasValidParams();
hasSignificantParameterChanges = false; hasSignificantParameterChanges = false;
@@ -256,7 +260,7 @@ public class FunctionEditorModel {
* {@link VariableUtilities#checkVariableConflict(List, Variable, VariableStorage, VariableConflictHandler)} * {@link VariableUtilities#checkVariableConflict(List, Variable, VariableStorage, VariableConflictHandler)}
* @param conflicts parameters whose storage conflicts * @param conflicts parameters whose storage conflicts
* @return return false to indicate conflicts have not been resolved and additional checks * @return return false to indicate conflicts have not been resolved and additional checks
* should be disconctinued. * should be discontinued.
* @see VariableConflictHandler * @see VariableConflictHandler
*/ */
private boolean handleConflicts(List<Variable> conflicts) { private boolean handleConflicts(List<Variable> conflicts) {
@@ -405,6 +409,18 @@ public class FunctionEditorModel {
notifyDataChanged(); notifyDataChanged();
} }
Namespace getNamespace() {
return functionData.getNamespace();
}
void setNamespace(Namespace ns) {
if (getNamespace().equals(ns)) {
return;
}
functionData.setNamespace(ns);
notifyDataChanged();
}
String getCallingConventionName() { String getCallingConventionName() {
return functionData.getCallingConventionName(); return functionData.getCallingConventionName();
} }
@@ -742,9 +758,12 @@ public class FunctionEditorModel {
if (b == canUseCustomStorage()) { if (b == canUseCustomStorage()) {
return; return;
} }
functionData.setUseCustomStorage(b); functionData.setUseCustomStorage(b);
isSignatureTransformed = !functionData.getFunctionSignatureText()
.equals(originalFunctionData.getFunctionSignatureText()); String signatureText = functionData.getFunctionSignatureText();
String originalSignatureText = originalFunctionData.getFunctionSignatureText();
isSignatureTransformed = !signatureText.equals(originalSignatureText);
notifyDataChanged(); notifyDataChanged();
} }
@@ -779,6 +798,11 @@ public class FunctionEditorModel {
function.setName(name, SourceType.USER_DEFINED); function.setName(name, SourceType.USER_DEFINED);
} }
Namespace namespace = functionData.getNamespace();
if (!namespace.equals(function.getParentNamespace())) {
function.setParentNamespace(namespace);
}
boolean isInline = functionData.isInline(); boolean isInline = functionData.isInline();
if (function.isInline() != isInline) { if (function.isInline() != isInline) {
function.setInline(isInline); function.setInline(isInline);
@@ -905,7 +929,7 @@ public class FunctionEditorModel {
return dt1.getLength() == dt2.getLength(); return dt1.getLength() == dt2.getLength();
} }
public void setFunctionData(FunctionDefinitionDataType functionDefinition) { public void setFunctionData(Namespace ns, FunctionDefinitionDataType functionDefinition) {
setName(functionDefinition.getName()); setName(functionDefinition.getName());
@@ -941,6 +965,8 @@ public class FunctionEditorModel {
functionData.updateParameterAndReturnStorage(); functionData.updateParameterAndReturnStorage();
functionData.setNamespace(ns);
notifyDataChanged(); notifyDataChanged();
} }
@@ -1010,7 +1036,9 @@ public class FunctionEditorModel {
void parseSignatureFieldText() throws ParseException, CancelledException { void parseSignatureFieldText() throws ParseException, CancelledException {
FunctionSignatureParser parser = FunctionSignatureParser parser =
new FunctionSignatureParser(program.getDataTypeManager(), dataTypeManagerService); 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 // Preserve calling convention and noreturn flag from current model
f.setNoReturn(functionData.hasNoReturn()); f.setNoReturn(functionData.hasNoReturn());
@@ -1021,10 +1049,31 @@ public class FunctionEditorModel {
// ignore // ignore
} }
setFunctionData(f); Namespace ns = createNamespace(result.namespace());
setFunctionData(ns, f);
isInParsingMode = false; 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() { int getFunctionNameStartPosition() {
return getFormalReturnType().getName().length() + 1; return getFormalReturnType().getName().length() + 1;
} }
@@ -40,6 +40,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Swing; import ghidra.util.Swing;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.VerticalLayout; 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 // the number of columns determines the default width of the add/edit label dialog
labelNameChoices.setColumns(20); labelNameChoices.setColumns(20);
labelNameChoices.setName("label.name.choices"); labelNameChoices.setName("label.name.choices");
GhidraComboBox<NamespaceWrapper> comboBox = new GhidraComboBox<>(); namespaceChoices = new GhidraComboBox<>();
namespaceChoices = comboBox;
primaryCheckBox = new GCheckBox("Primary"); primaryCheckBox = new GCheckBox("Primary");
primaryCheckBox.setMnemonic('P'); primaryCheckBox.setMnemonic('P');
@@ -636,12 +636,40 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
private void showNamespaceChooser() { private void showNamespaceChooser() {
NamespaceChooserDialog dialog = new NamespaceChooserDialog(); NamespaceChooserDialog dialog = new NamespaceChooserDialog();
Namespace namespace = dialog.getNameSpace(program); Namespace namespace = dialog.getNamespace(program);
if (namespace != null) { if (namespace != null) {
NamespaceCache.add(program, namespace); NamespaceCache.add(program, namespace);
initNamespaces(); initNamespaces();
namespaceChoices.setSelectedItem(new NamespaceWrapper(namespace)); 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() { private void addListeners() {
@@ -23,6 +23,9 @@ import ghidra.util.datastruct.LRUSet;
/** /**
* Static class for remember the last few namespaces used for a program. * 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 class NamespaceCache {
public static final int MAX_RECENTS = 10; public static final int MAX_RECENTS = 10;
@@ -48,7 +48,11 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
addCancelButton(); addCancelButton();
} }
public Namespace getNameSpace(Program program) { public void setText(String text) {
dropDownField.setText(text);
}
public Namespace getNamespace(Program program) {
List<Namespace> namespaces = gatherNamespaces(program); List<Namespace> namespaces = gatherNamespaces(program);
if (namespaces == null) { if (namespaces == null) {
// user cancelled while gathering namespaces // user cancelled while gathering namespaces
@@ -59,6 +63,10 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
return chosenNamespace; return chosenNamespace;
} }
public String getNamespaceText() {
return dropDownField.getText();
}
@Override @Override
protected void okCallback() { protected void okCallback() {
chosenNamespace = dropDownField.getSelectedValue(); chosenNamespace = dropDownField.getSelectedValue();
@@ -68,6 +76,7 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
chosenNamespace = null; chosenNamespace = null;
dropDownField.setText("");
close(); close();
} }
@@ -82,6 +91,7 @@ public class NamespaceChooserDialog extends DialogComponentProvider {
panel.add(new JLabel("Namespace: ")); panel.add(new JLabel("Namespace: "));
dropDownField = new DropDownSelectionTextField<>(namespaceModel); dropDownField = new DropDownSelectionTextField<>(namespaceModel);
dropDownField.setShowMatchingListOnEmptyText(true);
panel.add(dropDownField); panel.add(dropDownField);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
return panel; return panel;
@@ -21,6 +21,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.app.services.DataTypeQueryService; import ghidra.app.services.DataTypeQueryService;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.cparser.C.ParseException; import ghidra.app.util.cparser.C.ParseException;
import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
@@ -56,7 +57,8 @@ public class FunctionSignatureParser {
private DataTypeParser dataTypeParser; private DataTypeParser dataTypeParser;
private Map<String, DataType> dtMap = new HashMap<>(); 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 DataTypeManager destDataTypeManager;
private ParserDataTypeManagerService dtmService; private ParserDataTypeManagerService dtmService;
@@ -96,8 +98,16 @@ public class FunctionSignatureParser {
*/ */
public FunctionDefinitionDataType parse(FunctionSignature originalSignature, public FunctionDefinitionDataType parse(FunctionSignature originalSignature,
String signatureText) throws ParseException, CancelledException { 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(); dtMap.clear();
nameMap.clear(); replacedNameMap.clear();
if (dtmService != null) { if (dtmService != null) {
dtmService.clearCache(); // clear datatype selection cache dtmService.clearCache(); // clear datatype selection cache
} }
@@ -108,14 +118,18 @@ public class FunctionSignatureParser {
} }
String functionName = extractFunctionName(signatureText); String functionName = extractFunctionName(signatureText);
SymbolPath path = new SymbolPath(functionName);
SymbolPath nsPath = path.getParent();
String name = path.getName();
FunctionDefinitionDataType function = FunctionDefinitionDataType function =
new FunctionDefinitionDataType(functionName, destDataTypeManager); new FunctionDefinitionDataType(name, destDataTypeManager);
function.setReturnType(extractReturnType(signatureText)); function.setReturnType(extractReturnType(signatureText));
function.setArguments(extractArguments(signatureText)); function.setArguments(extractArguments(signatureText));
function.setVarArgs(hasVarArgs(signatureText)); function.setVarArgs(hasVarArgs(signatureText));
return function; return new FsParseResult(nsPath, function);
} }
private void initDataTypeMap(FunctionSignature signature) { private void initDataTypeMap(FunctionSignature signature) {
@@ -247,7 +261,7 @@ public class FunctionSignatureParser {
if (canParseName(name)) { if (canParseName(name)) {
return text; return text;
} }
nameMap.put(replacementName, name); replacedNameMap.put(replacementName, name);
return substitute(text, name, replacementName); return substitute(text, name, replacementName);
} }
@@ -299,8 +313,8 @@ public class FunctionSignatureParser {
} }
private String resolveName(String name) throws ParseException { private String resolveName(String name) throws ParseException {
if (nameMap.containsKey(name)) { if (replacedNameMap.containsKey(name)) {
return nameMap.get(name); return replacedNameMap.get(name);
} }
if (!canParseName(name)) { if (!canParseName(name)) {
throw new ParseException("Can't parse name: " + name); throw new ParseException("Can't parse name: " + name);
@@ -320,6 +334,14 @@ public class FunctionSignatureParser {
return !StringUtils.containsAny(text, "()<>,"); 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> * Provides a simple caching datatype manager service wrapper.<br>
* Implementation intended for use with {@link FunctionSignatureParser} * Implementation intended for use with {@link FunctionSignatureParser}
@@ -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.
@@ -15,34 +15,36 @@
*/ */
package ghidra.app.plugin.core.function.editor; package ghidra.app.plugin.core.function.editor;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;
import javax.swing.AbstractButton;
import javax.swing.ComboBoxModel;
import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellEditor;
import org.junit.*; import org.junit.*;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.DropDownSelectionTextField; import docking.widgets.DropDownSelectionTextField;
import docking.widgets.button.BrowseButton;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.app.cmd.function.DeleteFunctionCmd; import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin; import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.app.util.*;
import ghidra.app.util.datatype.DataTypeSelectionEditor; import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.test.*; import ghidra.test.*;
public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTest { public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTest {
public FunctionEditorDialogTest() {
super();
}
private TestEnv env; private TestEnv env;
private PluginTool tool; private PluginTool tool;
private AddressFactory addrFactory; private AddressFactory addrFactory;
@@ -64,10 +66,11 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
@After @After
public void tearDown() { public void tearDown() {
closeAllWindows();
env.dispose(); env.dispose();
} }
/** /*
* Tests that an invalid parameter type entry will generate the proper error message * 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. * 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")); 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 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) { private void setEditorText(TableCellEditor cellEditor, String text) {
DropDownSelectionTextField<?> textField = getDataTypeEditor(cellEditor); DropDownSelectionTextField<?> textField = getDataTypeEditor(cellEditor);
setText(textField, text); setText(textField, text);
@@ -106,7 +279,7 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
private FunctionEditorDialog editFunction() { private FunctionEditorDialog editFunction() {
performAction(editFunction, cb.getProvider(), false); performAction(editFunction, cb.getProvider(), false);
return waitForDialogComponent(null, FunctionEditorDialog.class, DEFAULT_WINDOW_TIMEOUT); return waitForDialogComponent(FunctionEditorDialog.class);
} }
private void finishEditing(final TableCellEditor cellEditor) { private void finishEditing(final TableCellEditor cellEditor) {
@@ -150,6 +323,11 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes
addrFactory = program.getAddressFactory(); addrFactory = program.getAddressFactory();
} }
private Function getFunction(String addr) {
FunctionManager fm = program.getFunctionManager();
return fm.getFunctionAt(addr(addr));
}
private void createFunctionAtEntry() { private void createFunctionAtEntry() {
FunctionManager fm = program.getFunctionManager(); FunctionManager fm = program.getFunctionManager();
Function f = fm.getFunctionAt(addr("0x1006420")); Function f = fm.getFunctionAt(addr("0x1006420"));
@@ -132,6 +132,7 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration
public void testExtractFunctionName() throws Exception { public void testExtractFunctionName() throws Exception {
assertEquals("bob", parser.extractFunctionName("void bob(int a)")); assertEquals("bob", parser.extractFunctionName("void bob(int a)"));
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 @Test
@@ -22,7 +22,6 @@ import java.util.List;
import org.junit.*; import org.junit.*;
import generic.test.AbstractGuiTest; import generic.test.AbstractGuiTest;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.cparser.C.ParseException; import ghidra.app.util.cparser.C.ParseException;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
@@ -40,7 +39,6 @@ public class FunctionEditorModelTest extends AbstractGuiTest {
private volatile boolean dataChangeCalled; private volatile boolean dataChangeCalled;
private Structure bigStruct; private Structure bigStruct;
private ProgramDB program; private ProgramDB program;
private DataTypeManagerService service;
private volatile boolean tableRowsChanged; private volatile boolean tableRowsChanged;
class MyModelChangeListener implements ModelChangeListener { class MyModelChangeListener implements ModelChangeListener {
@@ -1612,9 +1610,9 @@ public class FunctionEditorModelTest extends AbstractGuiTest {
assertEquals("R9D:4", storage.toString()); assertEquals("R9D:4", storage.toString());
model.setUseCustomizeStorage(false); 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 // 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.getReturnType().isEquivalent(new PointerDataType(bigStruct)));
assertTrue(model.getFormalReturnType().isEquivalent(bigStruct)); assertTrue(model.getFormalReturnType().isEquivalent(bigStruct));
@@ -1843,7 +1841,6 @@ public class FunctionEditorModelTest extends AbstractGuiTest {
model.setUseCustomizeStorage(true); model.setUseCustomizeStorage(true);
VariableStorage paramStorage1 = model.getParameters().get(0).getStorage(); VariableStorage paramStorage1 = model.getParameters().get(0).getStorage();
VariableStorage paramStorage2 = model.getParameters().get(1).getStorage();
VariableStorage paramStorage3 = model.getParameters().get(2).getStorage(); VariableStorage paramStorage3 = model.getParameters().get(2).getStorage();
model.setSignatureFieldText("int joe(int e, int c, int f, int g)"); 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"); * 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.
@@ -27,6 +27,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.VariableStorage; import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.*; import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SourceType;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.UndefinedFunction; import ghidra.util.UndefinedFunction;
@@ -99,7 +100,10 @@ public class SpecifyCPrototypeAction extends AbstractDecompilerAction {
if (useCustom) { if (useCustom) {
// Force custom storage // Force custom storage
model.setUseCustomizeStorage(true); 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()); model.setReturnStorage(functionPrototype.getReturnStorage());
parameters = model.getParameters(); parameters = model.getParameters();
for (int i = 0; i < decompParamCnt; i++) { 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.getEntryPoint().equals(hf.getFunction().getEntryPoint())) {
if (function.getSignatureSource() == SourceType.DEFAULT) { if (function.getSignatureSource() == SourceType.DEFAULT) {
model.setFunctionData(buildSignature(hf)); Namespace ns = function.getParentNamespace();
FunctionDefinitionDataType signature = buildSignature(hf);
model.setFunctionData(ns, signature);
verifyDynamicEditorModel(hf, model); verifyDynamicEditorModel(hf, model);
} }
else if (function.getReturnType() == DataType.DEFAULT) { 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 * 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. * 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 * @return returns the child level of this level
* @throws IllegalArgumentException if this is the source level, which is row 1 * @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 * <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 * 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. * to be arranged.
*/ */
public class BowTieExpandVerticesJob extends AbstractGraphTransitionJob<FcgVertex, FcgEdge> { public class BowTieExpandVerticesJob extends AbstractGraphTransitionJob<FcgVertex, FcgEdge> {
@@ -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.
@@ -31,7 +31,7 @@ import util.CollectionUtils;
* A container to house all newly added vertices (those being arranged) and the sources, or * A container to house all newly added vertices (those being arranged) and the sources, or
* 'from' vertices, of the new vertices. * '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 * 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 * 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 * 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 * Returns the text in combobox's editor text component
* @return 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"); * 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.
@@ -129,4 +129,9 @@ public class GlobalNamespace implements Namespace {
return false; return false;
} }
@Override
public int hashCode() {
return getClass().hashCode();
}
} }
@@ -79,7 +79,7 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator {
public void testChooseNamespace() { public void testChooseNamespace() {
runSwingLater(() -> { runSwingLater(() -> {
NamespaceChooserDialog dialog = new NamespaceChooserDialog(); NamespaceChooserDialog dialog = new NamespaceChooserDialog();
dialog.getNameSpace(program); dialog.getNamespace(program);
}); });
waitForDialogComponent(NamespaceChooserDialog.class); waitForDialogComponent(NamespaceChooserDialog.class);
captureDialog(); captureDialog();