diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm index 5b3c50ee17..0ed73247cc 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm @@ -327,6 +327,10 @@

This text field can be used to change the name of the function.

+

Namespace

+ +

This combo box and browse button can be use to change the function namespace.

+

Calling Convention

This field is a combobox that allows you to choose a calling convention from the list of diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/images/FunctionEditor.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/images/FunctionEditor.png index 9913c311e2..b2edbda959 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/images/FunctionEditor.png and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/images/FunctionEditor.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionData.java index a66dc66688..f89737e7a8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionData.java @@ -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; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionDataView.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionDataView.java index bfc48ca0ef..065f85bf95 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionDataView.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionDataView.java @@ -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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java index 9eb04cea1a..aa464e9beb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java @@ -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 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 getSelectableNamespaces() { + SequencedSet namespaces = new LinkedHashSet<>(); + addGlobalNamespace(namespaces); + addCurrentNamespace(namespaces); + addRecentNamespaces(namespaces); + return namespaces; + } + + private void addRecentNamespaces(SequencedSet namespaces) { + Program program = model.getProgram(); + List recentNamespaces = NamespaceCache.get(program); + if (recentNamespaces == null) { + return; + } + for (Namespace namespace : recentNamespaces) { + if (!namespaces.contains(namespace)) { + namespaces.add(namespace); + } + } + } + + private void addGlobalNamespace(SequencedSet namespaces) { + Program program = model.getProgram(); + Namespace globalNamespace = program.getGlobalNamespace(); + if (!namespaces.contains(globalNamespace)) { + namespaces.add(globalNamespace); + } + } + + private void addCurrentNamespace(SequencedSet 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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java index 6f8045b6b9..97426a2184 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java @@ -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 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; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java index 96d4edf86a..d691ecf807 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java @@ -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 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() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java index 3fc485eb75..7808e4993a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java @@ -23,6 +23,9 @@ import ghidra.util.datastruct.LRUSet; /** * Static class for remember the last few namespaces used for a program. + * + *

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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java index 43ddafac52..6b3a535f76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java @@ -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 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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java index d90cb4429b..a0485ffd63 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java @@ -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 dtMap = new HashMap<>(); - private Map nameMap = new HashMap<>(); + /** Stores parameter names that required name fixup for parsing to work correctly */ + private Map 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.
* Implementation intended for use with {@link FunctionSignatureParser} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java index f73c8f717b..af4a4ca16c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java @@ -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")); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java index 3bcf031939..2cc7388504 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java @@ -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 diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java index bd295ad46e..a93915548a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java @@ -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)"); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java index aeffece704..4b6ce3fe58 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java @@ -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) { diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java index 775b9396e0..821ab24c2f 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java @@ -145,8 +145,8 @@ public class FcgLevel implements Comparable { /** * 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 */ diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java index 2f8397f6ca..035d9f571a 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java @@ -38,7 +38,7 @@ import ghidra.util.Msg; * *

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 { diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java index c4cfa04467..42b0a2fa00 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java @@ -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. * - *

This offers exiting vertices and new vertices pre-sorted by position in the graph in + *

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 diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java index b1489076ff..af7831455c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java @@ -131,6 +131,12 @@ public class GhidraComboBox extends JComboBox 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 diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java index 6d94560f4d..dc25dabfa7 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java @@ -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(); + } + } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java index d5fc118b32..4aa549d449 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java @@ -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();