From bc8e56bd60ecdca7e6fca2e04ab4b910aaeec334 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Thu, 25 Feb 2021 18:33:00 -0500 Subject: [PATCH] GP-356 refactor of EditFunctionSignatureDialog and the various extensions. --- .../datamgr/editor/DataTypeEditorManager.java | 192 +++---- .../AbstractEditFunctionSignatureDialog.java | 435 ++++++++++++++++ .../function/EditFunctionSignatureDialog.java | 470 +++++------------- .../FunctionDataTypeHTMLRepresentation.java | 2 +- .../datamgr/DataTypeManagerPluginTest.java | 12 +- .../actions/OverridePrototypeAction.java | 194 +++++--- 6 files changed, 778 insertions(+), 527 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/AbstractEditFunctionSignatureDialog.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java index 2743e42c38..e52ef5cd58 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java @@ -18,25 +18,21 @@ package ghidra.app.plugin.core.datamgr.editor; import java.util.ArrayList; import java.util.List; -import javax.swing.ComboBoxModel; -import javax.swing.JPanel; - import docking.ComponentProvider; import docking.actions.DockingToolActions; import docking.actions.SharedDockingActionPlaceholder; -import docking.widgets.checkbox.GCheckBox; -import docking.widgets.combobox.GhidraComboBox; -import docking.widgets.label.GLabel; import ghidra.app.plugin.core.compositeeditor.*; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.function.EditFunctionSignatureDialog; +import ghidra.app.plugin.core.function.AbstractEditFunctionSignatureDialog; import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.FunctionSignature; +import ghidra.program.model.listing.Program; import ghidra.util.*; -import ghidra.util.exception.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.DuplicateNameException; /** * Manages program and archive data type editors. @@ -238,8 +234,8 @@ public class DataTypeEditorManager list.add(editor); } } - for (int i = 0; i < list.size(); i++) { - dismissEditor(list.get(i)); + for (EditorProvider element : list) { + dismissEditor(element); } } @@ -519,55 +515,10 @@ public class DataTypeEditorManager editFunctionSignature(category, functionDefinition); } - private void editFunctionSignature(final Category category, - final FunctionDefinition functionDefinition) { - - Function function = - new UndefinedFunction(plugin.getProgram(), plugin.getProgram().getMinAddress()) { - @Override - public String getCallingConventionName() { - if (functionDefinition == null) { - return super.getCallingConventionName(); - } - return functionDefinition.getGenericCallingConvention().toString(); - } - - @Override - public void setCallingConvention(String name) throws InvalidInputException { - // no-op; we handle this in the editor dialog - } - - @Override - public void setInline(boolean isInline) { - // can't edit this from the DataTypeManager - } - - @Override - public void setNoReturn(boolean hasNoReturn) { - // can't edit this from the DataTypeManager - } - - @Override - public FunctionSignature getSignature() { - if (functionDefinition != null) { - return functionDefinition; - } - return super.getSignature(); - } - - @Override - public String getName() { - if (functionDefinition != null) { - return functionDefinition.getName(); - } - return "newFunction"; - } - }; - - // DT how do I do the same as the other creates. + private void editFunctionSignature(Category category, FunctionDefinition functionDefinition) { PluginTool tool = plugin.getTool(); DTMEditFunctionSignatureDialog editSigDialog = new DTMEditFunctionSignatureDialog( - plugin.getTool(), "Edit Function Signature", function, category, functionDefinition); + plugin.getTool(), "Edit Function Signature", category, functionDefinition); editSigDialog.setHelpLocation( new HelpLocation("DataTypeManagerPlugin", "Function_Definition")); tool.showDialog(editSigDialog); @@ -577,64 +528,73 @@ public class DataTypeEditorManager // Inner Classes //================================================================================================== - private class DTMEditFunctionSignatureDialog extends EditFunctionSignatureDialog { + /** + * DTMEditFunctionSignatureDialog provides the ability to edit the + * function signature associated with a specific {@link FunctionDefinition}. + * Use of this editor requires the presence of the tool-based datatype manager service. + */ + private class DTMEditFunctionSignatureDialog extends AbstractEditFunctionSignatureDialog { + private final FunctionDefinition functionDefinition; + private final FunctionSignature oldSignature; private final Category category; - private final FunctionDefinition functionDefinitionDataType; - DTMEditFunctionSignatureDialog(PluginTool pluginTool, String title, Function function, - Category category, FunctionDefinition functionDefinition) { - super(pluginTool, title, function); + DTMEditFunctionSignatureDialog(PluginTool pluginTool, String title, Category category, + FunctionDefinition functionDefinition) { + super(pluginTool, title, false, false, false); + this.functionDefinition = functionDefinition; this.category = category; - this.functionDefinitionDataType = functionDefinition; - - if (functionDefinitionDataType != null) { - setCallingConvention( - functionDefinitionDataType.getGenericCallingConvention().toString()); - } + this.oldSignature = buildSignature(); } - @Override - protected void installCallingConventionWidget(JPanel parentPanel) { - callingConventionComboBox = new GhidraComboBox<>(); - GenericCallingConvention[] values = GenericCallingConvention.values(); - String[] choices = new String[values.length]; - for (int i = 0; i < values.length; i++) { - choices[i] = values[i].toString(); - } - - setCallingConventionChoices(choices); - parentPanel.add(new GLabel("Calling Convention:")); - parentPanel.add(callingConventionComboBox); - } - - @Override - protected void installInlineWidget(JPanel parentPanel) { - inlineCheckBox = new GCheckBox("Inline"); - } - - @Override - protected void installNoReturnWidget(JPanel parentPanel) { - noReturnCheckBox = new GCheckBox("No Return"); - } - - @Override - protected void installCallFixupWidget(JPanel parentPanel) { - // don't add this panel - } - - @Override - protected void setCallingConvention(String callingConvention) { - ComboBoxModel model = callingConventionComboBox.getModel(); - int size = model.getSize(); - for (int i = 0; i < size; i++) { - Object item = model.getElementAt(i); - if (item.equals(callingConvention)) { - callingConventionComboBox.setSelectedItem(callingConvention); - return; + private FunctionSignature buildSignature() { + if (functionDefinition != null) { + if (category.getDataTypeManager() != functionDefinition.getDataTypeManager()) { + throw new IllegalArgumentException( + "functionDefinition and category must have same Datatypemanager"); } + return functionDefinition; } + return new FunctionDefinitionDataType("newFunction"); + } - callingConventionComboBox.setSelectedItem(GenericCallingConvention.unknown); + @Override + protected String[] getSupportedCallFixupNames() { + return null; // Call fixup not supported on FunctionDefinition + } + + @Override + protected String getCallFixupName() { + return null; // Call fixup not supported on FunctionDefinition + } + + @Override + protected FunctionSignature getFunctionSignature() { + return oldSignature; + } + + @Override + protected String getPrototypeString() { + return getFunctionSignature().getPrototypeString(); + } + + @Override + protected String getCallingConventionName() { + return getFunctionSignature().getGenericCallingConvention().toString(); + } + + @Override + protected List getCallingConventionNames() { + GenericCallingConvention[] values = GenericCallingConvention.values(); + List choices = new ArrayList<>(); + for (GenericCallingConvention value : values) { + choices.add(value.toString()); + } + return choices; + } + + @Override + protected DataTypeManager getDataTypeManager() { + return category.getDataTypeManager(); } @Override @@ -657,9 +617,9 @@ public class DataTypeEditorManager GenericCallingConvention.getGenericCallingConvention(getCallingConvention()); newDefinition.setGenericCallingConvention(callingConvention); - DataTypeManager manager = category.getDataTypeManager(); + DataTypeManager manager = getDataTypeManager(); SourceArchive sourceArchive = manager.getLocalSourceArchive(); - if (functionDefinitionDataType == null) { + if (functionDefinition == null) { newDefinition.setSourceArchive(sourceArchive); newDefinition.setCategoryPath(category.getCategoryPath()); int id = manager.startTransaction("Create Function Definition"); @@ -669,14 +629,14 @@ public class DataTypeEditorManager else { int id = manager.startTransaction("Edit Function Definition"); try { - if (!functionDefinitionDataType.getName().equals(newDefinition.getName())) { - functionDefinitionDataType.setName(newDefinition.getName()); + if (!functionDefinition.getName().equals(newDefinition.getName())) { + functionDefinition.setName(newDefinition.getName()); } - functionDefinitionDataType.setArguments(newDefinition.getArguments()); - functionDefinitionDataType.setGenericCallingConvention( + functionDefinition.setArguments(newDefinition.getArguments()); + functionDefinition.setGenericCallingConvention( newDefinition.getGenericCallingConvention()); - functionDefinitionDataType.setReturnType(newDefinition.getReturnType()); - functionDefinitionDataType.setVarArgs(newDefinition.hasVarArgs()); + functionDefinition.setReturnType(newDefinition.getReturnType()); + functionDefinition.setVarArgs(newDefinition.hasVarArgs()); } catch (InvalidNameException | DuplicateNameException e) { // not sure why we are squashing this? ...assuming this can't happen diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/AbstractEditFunctionSignatureDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/AbstractEditFunctionSignatureDialog.java new file mode 100644 index 0000000000..43e106e0f1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/AbstractEditFunctionSignatureDialog.java @@ -0,0 +1,435 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.function; + +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.util.List; + +import javax.swing.*; + +import docking.DialogComponentProvider; +import docking.widgets.checkbox.GCheckBox; +import docking.widgets.combobox.GhidraComboBox; +import docking.widgets.label.GDLabel; +import docking.widgets.label.GLabel; +import ghidra.app.services.DataTypeManagerService; +import ghidra.app.util.cparser.C.ParseException; +import ghidra.app.util.parser.FunctionSignatureParser; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.FunctionDefinitionDataType; +import ghidra.program.model.listing.FunctionSignature; +import ghidra.util.exception.CancelledException; + +/** + * EditFunctionSignatureDialog provides an abstract implementation + * a function signature editor. Use of this editor requires the presence of the tool-based + * datatype manager service. + */ +public abstract class AbstractEditFunctionSignatureDialog extends DialogComponentProvider { + + private static final String NONE_CHOICE = "-NONE-"; + private static int SIGNATURE_COLUMNS = 60; + + protected JLabel signatureLabel; + protected JTextField signatureField; + protected JComboBox callingConventionComboBox; + protected JComboBox callFixupComboBox; + protected JCheckBox inlineCheckBox; + protected JCheckBox noReturnCheckBox; + + protected boolean allowInLine; + protected boolean allowNoReturn; + protected boolean allowCallFixup; + + protected PluginTool tool; + + // Due to delayed initialization and tests not actually displaying dialog + // we will track function info initialization + boolean initialized = false; + + /** + * Abstract function signature editor + * + * @param tool A reference to the active tool. + * @param title The title of the dialog. + * @param allowInLine true if in-line attribute control should be included + * @param allowNoReturn true if no-return attribute control should be added + * @param allowCallFixup true if call-fixup choice should be added + */ + public AbstractEditFunctionSignatureDialog(PluginTool tool, String title, boolean allowInLine, + boolean allowNoReturn, boolean allowCallFixup) { + + super(title, true, true, true, false); + this.tool = tool; + this.allowInLine = allowInLine; + this.allowNoReturn = allowNoReturn; + this.allowCallFixup = allowCallFixup; + + addWorkPanel(buildMainPanel()); + addOKButton(); + addCancelButton(); + setDefaultButton(okButton); + setRememberSize(true); + } + + @Override + public JComponent getComponent() { + setFunctionInfo(); //delay update for after construction + return super.getComponent(); + } + + /** + * @return DataTypeManager associated with function or function definition + */ + protected abstract DataTypeManager getDataTypeManager(); + + /** + * @return optional initial function signature which can assist parse with + * identifying referenced datatypes within signature + */ + protected abstract FunctionSignature getFunctionSignature(); + + /** + * @return the initial signature string for the dialog + */ + protected abstract String getPrototypeString(); + + /** + * @return initial calling convention name + */ + protected abstract String getCallingConventionName(); + + /** + * @return list of acceptable calling convention names + */ + protected abstract List getCallingConventionNames(); + + /** + * @return initial in-line attribute value + */ + protected boolean isInline() { + return false; + } + + /** + * @return initial no-return attribute value + */ + protected boolean hasNoReturn() { + return false; + } + + /** + * @return initial call-fixup name or null if n/a + */ + protected abstract String getCallFixupName(); + + /** + * @return array of allowed call fixup names or null + */ + protected abstract String[] getSupportedCallFixupNames(); + + /** + * Method must be invoked following construction to fetch function info + * and update components. + */ + private void setFunctionInfo() { + if (initialized) { + return; + } + initialized = true; + + signatureField.setText(getPrototypeString()); + setCallingConventionChoices(); + callingConventionComboBox.setSelectedItem(getCallingConventionName()); + if (allowInLine) { + inlineCheckBox.setSelected(isInline()); + } + if (allowNoReturn) { + noReturnCheckBox.setSelected(hasNoReturn()); + } + if (allowCallFixup) { + setCallFixupChoices(); + + String callFixupName = getCallFixupName(); + if (callFixupName != null) { + callFixupComboBox.setSelectedItem(callFixupName); + } + } + } + + private JPanel buildMainPanel() { + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 2)); + mainPanel.add(buildSignaturePanel()); + mainPanel.add(buildAttributePanel()); + if (allowCallFixup) { + installCallFixupWidget(mainPanel); + } + return mainPanel; + } + + private void installCallFixupWidget(JPanel parentPanel) { + JPanel callFixupPanel = buildCallFixupPanel(); + parentPanel.add(callFixupPanel != null ? callFixupPanel : buildSpacerPanel()); + } + + private JPanel buildSignaturePanel() { + JPanel signaturePanel = new JPanel(); + signaturePanel.setLayout(new BoxLayout(signaturePanel, BoxLayout.X_AXIS)); + + signatureField = new JTextField(SIGNATURE_COLUMNS); + signatureLabel = new GDLabel("Signature:"); + signaturePanel.add(signatureLabel); + signaturePanel.add(signatureField); + + signaturePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + return signaturePanel; + } + + private Component buildSpacerPanel() { + JPanel panel = new JPanel(); + + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.add(Box.createVerticalStrut(20)); + + return panel; + } + + private JPanel buildAttributePanel() { + JPanel attributePanel = new JPanel(); + attributePanel.setLayout(new BoxLayout(attributePanel, BoxLayout.X_AXIS)); + attributePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + installCallingConventionWidget(attributePanel); + if (allowInLine) { + installInlineWidget(attributePanel); + } + if (allowNoReturn) { + installNoReturnWidget(attributePanel); + } + attributePanel.add(Box.createGlue()); + + return attributePanel; + } + + private void installCallingConventionWidget(JPanel parentPanel) { + callingConventionComboBox = new GhidraComboBox<>(); + parentPanel.add(new GLabel("Calling Convention:")); + parentPanel.add(callingConventionComboBox); + } + + private void installInlineWidget(JPanel parentPanel) { + inlineCheckBox = new GCheckBox("Inline"); + inlineCheckBox.addChangeListener(e -> { + if (inlineCheckBox.isSelected() && callFixupComboBox != null) { + callFixupComboBox.setSelectedItem(NONE_CHOICE); + } + }); + parentPanel.add(inlineCheckBox); + } + + private void installNoReturnWidget(JPanel parentPanel) { + noReturnCheckBox = new GCheckBox("No Return"); + parentPanel.add(noReturnCheckBox); + } + + private JPanel buildCallFixupPanel() { + + if (allowCallFixup) { + return null; + } + + JPanel callFixupPanel = new JPanel(); + callFixupPanel.setLayout(new BoxLayout(callFixupPanel, BoxLayout.X_AXIS)); + + callFixupComboBox = new GhidraComboBox<>(); + callFixupComboBox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.DESELECTED) { + return; + } + if (!NONE_CHOICE.equals(e.getItem())) { + inlineCheckBox.setSelected(false); + } + }); + + callFixupPanel.add(new GLabel("Call-Fixup:")); + callFixupPanel.add(callFixupComboBox); + + callFixupPanel.add(Box.createGlue()); + callFixupPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + return callFixupPanel; + } + + /** + * @return plugin tool for which dialog was constructed + */ + protected PluginTool getTool() { + return tool; + } + + private String getSignature() { + return signatureField.getText(); + } + + private void setCallingConventionChoices() { + callingConventionComboBox.removeAllItems(); + for (String element : getCallingConventionNames()) { + callingConventionComboBox.addItem(element); + } + } + + /** + * @return current calling convention selection from dialog + */ + protected String getCallingConvention() { + return (String) callingConventionComboBox.getSelectedItem(); + } + + /** + * @return current in-line attribute value from dialog + */ + protected boolean isInlineSelected() { + return inlineCheckBox != null ? inlineCheckBox.isSelected() : false; + } + + /** + * @return current no-return attribute value from dialog + */ + protected boolean hasNoReturnSelected() { + return noReturnCheckBox != null ? noReturnCheckBox.isSelected() : false; + } + + private void setCallFixupChoices() { + String[] callFixupNames = getSupportedCallFixupNames(); + callFixupComboBox.addItem(NONE_CHOICE); + if (callFixupNames != null) { + for (String element : callFixupNames) { + callFixupComboBox.addItem(element); + } + } + } + + /** + * @return current call fixup selection from dialog or null + */ + protected String getCallFixupSelection() { + if (callFixupComboBox != null) { + String callFixup = (String) callFixupComboBox.getSelectedItem(); + if (callFixup != null && !NONE_CHOICE.equals(callFixup)) { + return callFixup; + } + } + return null; + } + + /** + * This method gets called when the user clicks on the OK Button. The base + * class calls this method. This method will invoke {@link #applyChanges()} + * and close dialog if that method returns true. If false is returned, the + * {@link #applyChanges()} method should display a status message to indicate + * the failure. + */ + @Override + protected void okCallback() { + // only close the dialog if the user made valid changes + try { + if (applyChanges()) { + close(); + } + } + catch (CancelledException e) { + // ignore - do not close + } + } + + @Override + protected void cancelCallback() { + setStatusText(""); + close(); + } + + /** + * Called when the user initiates changes that need to be applied to the + * underlying function or function definition + * + * @return true if applied successfully, otherwise false which will keep + * dialog displayed (a status message should bet set) + * @throws CancelledException if operation cancelled by user + */ + protected abstract boolean applyChanges() throws CancelledException; + + /** + * Perform parse of current user-specified function signature (see {@link #getSignature()}) + * and return valid {@link FunctionDefinitionDataType} if parse successful. + * @return function definition data type if parse successful, otherwise null + * @throws CancelledException if function signature entry cancelled + */ + protected final FunctionDefinitionDataType parseSignature() throws CancelledException { + setFunctionInfo(); // needed for testing which never shows dialog + FunctionSignatureParser parser = new FunctionSignatureParser( + getDataTypeManager(), tool.getService(DataTypeManagerService.class)); + try { + // FIXME: Parser returns FunctionDefinition which only supports GenericCallingConventions + return parser.parse(getFunctionSignature(), getSignature()); + } + catch (ParseException e) { + setStatusText("Invalid Signature: " + e.getMessage()); + } + return null; + } + + /** + * Determine if user-specified function signature has been modified from original + * @return true if modified signature has been entered, else false + */ + protected final boolean isSignatureChanged() { + return !getSignature().equals(getPrototypeString()); + } + + /** + * Determine if user has changed the selected calling convention from the original + * @return true if a change in the selected calling convention has been made + */ + protected final boolean isCallingConventionChanged() { + String current = getCallingConventionName(); + if (current == null && this.getCallingConvention() == null) { + return false; + } + if (current == null && this.getCallingConvention().equals("default")) { + return false; + } + if (current == null && this.getCallingConvention().equals("unknown")) { + return false; + } + if (current == null) { + return true; + } + if (current.equals(getCallingConvention())) { + return false; + } + return true; + } + + @Override + protected void dialogShown() { + signatureField.selectAll(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/EditFunctionSignatureDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/EditFunctionSignatureDialog.java index dcd4bfdcba..270b9440a7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/EditFunctionSignatureDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/EditFunctionSignatureDialog.java @@ -15,300 +15,122 @@ */ package ghidra.app.plugin.core.function; -import java.awt.Component; -import java.awt.event.ItemEvent; import java.util.List; -import javax.swing.*; - -import docking.DialogComponentProvider; -import docking.widgets.checkbox.GCheckBox; -import docking.widgets.combobox.GhidraComboBox; -import docking.widgets.label.GDLabel; -import docking.widgets.label.GLabel; import ghidra.app.cmd.function.ApplyFunctionSignatureCmd; -import ghidra.app.services.DataTypeManagerService; -import ghidra.app.util.cparser.C.ParseException; -import ghidra.app.util.parser.FunctionSignatureParser; import ghidra.framework.cmd.Command; import ghidra.framework.cmd.CompoundCmd; import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.FunctionDefinitionDataType; -import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.FunctionSignature; import ghidra.program.model.symbol.SourceType; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.exception.InvalidInputException; /** - * EditFunctionSignatureDialog provides the ability to edit function - * signatures. Use of this editor requires the presence of the tool-based - * datatype manager service. + * EditFunctionSignatureDialog provides the ability to edit the + * function signature associated with a specific {@link Function}. + * Use of this editor requires the presence of the tool-based datatype manager service. */ -public class EditFunctionSignatureDialog extends DialogComponentProvider { +public class EditFunctionSignatureDialog extends AbstractEditFunctionSignatureDialog { - private static final String NONE_CHOICE = "-NONE-"; - - protected JLabel signatureLabel; - protected JTextField signatureField; - protected JComboBox callingConventionComboBox; - protected JComboBox callFixupComboBox; - protected JCheckBox inlineCheckBox; - protected JCheckBox noReturnCheckBox; - - protected PluginTool tool; - protected Function function; - protected String oldFunctionName; - protected String oldFunctionSignature; + protected final Function function; + protected final String oldFunctionSignature; /** - * This class is not meant to be instantiated directly, but rather by - * subclasses. - * - * @param plugin A reference to the FunctionPlugin. + * Edit function signature for a specified Function + * @param tool A reference to the active tool. * @param title The title of the dialog. * @param function the function which is having its signature edited. */ public EditFunctionSignatureDialog(PluginTool tool, String title, final Function function) { - - super(title, true, true, true, false); - this.tool = tool; + super(tool, title, allowInLine(function), true, allowCallFixup(function)); this.function = function; - this.oldFunctionName = function.getName(); this.oldFunctionSignature = function.getSignature().getPrototypeString(); - - addWorkPanel(buildMainPanel()); - addOKButton(); - addCancelButton(); - setDefaultButton(okButton); - setFunctionInfo(); - setRememberSize(true); } - protected void setFunctionInfo() { - setSignature(function.getSignature().getPrototypeString()); - setCallingConvention(function.getCallingConventionName()); - setInlineSelected(function.isInline()); - inlineCheckBox.setEnabled(!getAffectiveFunction(function).isExternal()); - setNoReturnSelected(function.hasNoReturn()); + protected EditFunctionSignatureDialog(PluginTool tool, String title, final Function function, + boolean allowInLine, boolean allowNoReturn, boolean allowCallFixup) { + super(tool, title, allowInLine, allowNoReturn, allowCallFixup); + this.function = function; + this.oldFunctionSignature = function.getSignature().getPrototypeString(); + } + + @Override + protected FunctionSignature getFunctionSignature() { + return function.getSignature(); + } + + @Override + protected String getPrototypeString() { + return oldFunctionSignature; + } + + @Override + protected String getCallingConventionName() { + return function.getCallingConventionName(); + } + + @Override + protected List getCallingConventionNames() { + return function.getProgram().getFunctionManager().getCallingConventionNames(); + } + + @Override + protected boolean isInline() { + return function.isInline(); + } + + @Override + protected boolean hasNoReturn() { + return function.hasNoReturn(); + } + + @Override + protected String getCallFixupName() { + return function.getCallFixup(); + } + + private static String[] getCallFixupNames(Function function) { + String[] callFixupNames = + function.getProgram().getCompilerSpec().getPcodeInjectLibrary().getCallFixupNames(); + if (callFixupNames.length == 0) { + return null; + } + return callFixupNames; + } + + @Override + protected String[] getSupportedCallFixupNames() { + return getCallFixupNames(function); + } + + @Override + protected DataTypeManager getDataTypeManager() { + return function.getProgram().getDataTypeManager(); } /** * Get the effective function to which changes will be made. This * will be the same as function unless it is a thunk in which case * the returned function will be the ultimate non-thunk function. - * @param f + * @param f function * @return non-thunk function */ - protected Function getAffectiveFunction(Function f) { + private static Function getEffectiveFunction(Function f) { return f.isThunk() ? f.getThunkedFunction(true) : f; } - private JPanel buildMainPanel() { - JPanel mainPanel = new JPanel(); - mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 2)); - mainPanel.add(buildSignaturePanel()); - mainPanel.add(buildAttributePanel()); - - installCallFixupWidget(mainPanel); - - return mainPanel; + private static boolean allowInLine(Function function) { + return !getEffectiveFunction(function).isExternal(); } - protected void installCallFixupWidget(JPanel parentPanel) { - JPanel callFixupPanel = buildCallFixupPanel(); - parentPanel.add(callFixupPanel != null ? callFixupPanel : buildSpacerPanel()); - } - - private JPanel buildSignaturePanel() { - JPanel signaturePanel = new JPanel(); - signaturePanel.setLayout(new BoxLayout(signaturePanel, BoxLayout.X_AXIS)); - - String signature = function.getPrototypeString(false, false); - signatureField = new JTextField(signature.length()); // add some extra room to edit - signatureField.setText(signature); - signatureLabel = new GDLabel("Signature:"); - signaturePanel.add(signatureLabel); - signaturePanel.add(signatureField); - - signaturePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - return signaturePanel; - } - - private Component buildSpacerPanel() { - JPanel panel = new JPanel(); - - panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - panel.add(Box.createVerticalStrut(20)); - - return panel; - } - - private JPanel buildAttributePanel() { - JPanel attributePanel = new JPanel(); - attributePanel.setLayout(new BoxLayout(attributePanel, BoxLayout.X_AXIS)); - attributePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - installCallingConventionWidget(attributePanel); - installInlineWidget(attributePanel); - installNoReturnWidget(attributePanel); - attributePanel.add(Box.createGlue()); - - return attributePanel; - } - - protected void installCallingConventionWidget(JPanel parentPanel) { - callingConventionComboBox = new GhidraComboBox<>(); - List callingConventions = - function.getProgram().getFunctionManager().getCallingConventionNames(); - String[] choices = callingConventions.toArray(new String[callingConventions.size()]); - setCallingConventionChoices(choices); - parentPanel.add(new GLabel("Calling Convention:")); - parentPanel.add(callingConventionComboBox); - } - - protected void installInlineWidget(JPanel parentPanel) { - inlineCheckBox = new GCheckBox("Inline"); - inlineCheckBox.addChangeListener(e -> { - if (inlineCheckBox.isSelected() && callFixupComboBox != null) { - callFixupComboBox.setSelectedItem(NONE_CHOICE); - } - }); - parentPanel.add(inlineCheckBox); - } - - protected void installNoReturnWidget(JPanel parentPanel) { - noReturnCheckBox = new GCheckBox("No Return"); - parentPanel.add(noReturnCheckBox); - } - - private JPanel buildCallFixupPanel() { - - String[] callFixupNames = - function.getProgram().getCompilerSpec().getPcodeInjectLibrary().getCallFixupNames(); - if (callFixupNames.length == 0) { - return null; - } - - JPanel callFixupPanel = new JPanel(); - callFixupPanel.setLayout(new BoxLayout(callFixupPanel, BoxLayout.X_AXIS)); - - callFixupComboBox = new GhidraComboBox<>(); - callFixupComboBox.addItem(NONE_CHOICE); - for (String element : callFixupNames) { - callFixupComboBox.addItem(element); - } - - callFixupComboBox.addItemListener(e -> { - if (e.getStateChange() == ItemEvent.DESELECTED) { - return; - } - if (!NONE_CHOICE.equals(e.getItem())) { - inlineCheckBox.setSelected(false); - } - }); - - String callFixupName = function.getCallFixup(); - if (callFixupName != null) { - callFixupComboBox.setSelectedItem(callFixupName); - } - - callFixupPanel.add(new GLabel("Call-Fixup:")); - callFixupPanel.add(callFixupComboBox); - - callFixupPanel.add(Box.createGlue()); - callFixupPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - return callFixupPanel; - } - - protected PluginTool getTool() { - return tool; - } - - protected Program getProgram() { - return function.getProgram(); - } - - protected Function getFunction() { - return function; - } - - public String getSignature() { - return signatureField.getText(); - } - - protected void setSignature(String signature) { - signatureField.setText(signature); - } - - protected void setCallingConventionChoices(String[] callingConventions) { - callingConventionComboBox.removeAllItems(); - for (String element : callingConventions) { - callingConventionComboBox.addItem(element); - } - } - - protected String getCallingConvention() { - return (String) callingConventionComboBox.getSelectedItem(); - } - - protected void setCallingConvention(String callingConvention) { - callingConventionComboBox.setSelectedItem(callingConvention); - } - - protected boolean isInlineSelected() { - return inlineCheckBox.isSelected(); - } - - protected void setInlineSelected(boolean selected) { - inlineCheckBox.setSelected(selected); - } - - protected boolean hasNoReturnSelected() { - return noReturnCheckBox.isSelected(); - } - - protected void setNoReturnSelected(boolean selected) { - noReturnCheckBox.setSelected(selected); - } - - protected String getCallFixupSelection() { - if (callFixupComboBox != null) { - String callFixup = (String) callFixupComboBox.getSelectedItem(); - if (callFixup != null && !NONE_CHOICE.equals(callFixup)) { - return callFixup; - } - } - return null; - } - - /** - * This method gets called when the user clicks on the OK Button. The base - * class calls this method. - */ - @Override - protected void okCallback() { - // only close the dialog if the user made valid changes - try { - if (applyChanges()) { - close(); - } - } - catch (CancelledException e) { - // ignore - do not close - } - } - - @Override - protected void cancelCallback() { - setStatusText(""); - close(); + private static boolean allowCallFixup(Function function) { + return getCallFixupNames(function) != null; } /** @@ -318,6 +140,7 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider { * @return true if the command was successfully created. * @throws CancelledException if operation cancelled by user */ + @Override protected boolean applyChanges() throws CancelledException { // create the command Command command = createCommand(); @@ -327,7 +150,7 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider { } // run the command - if (!getTool().execute(command, getProgram())) { + if (!getTool().execute(command, function.getProgram())) { setStatusText(command.getStatusMsg()); return false; } @@ -336,25 +159,16 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider { return true; } - protected FunctionDefinitionDataType parseSignature() throws CancelledException { - FunctionSignatureParser parser = new FunctionSignatureParser( - getProgram().getDataTypeManager(), tool.getService(DataTypeManagerService.class)); - try { - return parser.parse(getFunction().getSignature(), getSignature()); - } - catch (ParseException e) { - setStatusText("Invalid Signature: " + e.getMessage()); - } - return null; - } - private Command createCommand() throws CancelledException { Command cmd = null; - if (!getSignature().equals(this.oldFunctionSignature) || !isSameCallingConvention() || + if (isSignatureChanged() || isCallingConventionChanged() || (function.getSignatureSource() == SourceType.DEFAULT)) { FunctionDefinitionDataType definition = parseSignature(); + if (definition == null) { + return null; + } cmd = new ApplyFunctionSignatureCmd(function.getEntryPoint(), definition, SourceType.USER_DEFINED, true, true); } @@ -394,85 +208,67 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider { return errMsg; } }); - compoundCommand.add(new Command() { - @Override - public boolean applyTo(DomainObject obj) { - function.setInline(isInlineSelected()); - return true; - } + if (allowInLine) { + compoundCommand.add(new Command() { + @Override + public boolean applyTo(DomainObject obj) { + function.setInline(isInlineSelected()); + return true; + } - @Override - public String getName() { - return "Update Function Inline Flag"; - } + @Override + public String getName() { + return "Update Function Inline Flag"; + } - @Override - public String getStatusMsg() { - return null; - } - }); - compoundCommand.add(new Command() { - @Override - public boolean applyTo(DomainObject obj) { - function.setNoReturn(hasNoReturnSelected()); - return true; - } + @Override + public String getStatusMsg() { + return null; + } + }); + } + if (allowNoReturn) { + compoundCommand.add(new Command() { + @Override + public boolean applyTo(DomainObject obj) { + function.setNoReturn(hasNoReturnSelected()); + return true; + } - @Override - public String getName() { - return "Update Function No Return Flag"; - } + @Override + public String getName() { + return "Update Function No Return Flag"; + } - @Override - public String getStatusMsg() { - return null; - } - }); - compoundCommand.add(new Command() { - @Override - public boolean applyTo(DomainObject obj) { - function.setCallFixup(getCallFixupSelection()); - return true; - } + @Override + public String getStatusMsg() { + return null; + } + }); + } + if (allowCallFixup) { + compoundCommand.add(new Command() { + @Override + public boolean applyTo(DomainObject obj) { + function.setCallFixup(getCallFixupSelection()); + return true; + } - @Override - public String getName() { - return "Update Function Call-Fixup"; - } + @Override + public String getName() { + return "Update Function Call-Fixup"; + } - @Override - public String getStatusMsg() { - return null; - } - }); + @Override + public String getStatusMsg() { + return null; + } + }); + } if (cmd != null) { compoundCommand.add(cmd); } return compoundCommand; } - private boolean isSameCallingConvention() { - PrototypeModel conv = function.getCallingConvention(); - if (conv == null && this.getCallingConvention() == null) { - return true; - } - if (conv == null && this.getCallingConvention().equals("default")) { - return true; - } - if (conv == null && this.getCallingConvention().equals("unknown")) { - return true; - } - if (conv == null) { - return false; - } - if (conv.getName().equals(this.getCallingConvention())) { - return true; - } - return false; - } - - @Override - protected void dialogShown() { - signatureField.selectAll(); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java index d2c780c871..ef8083b8fa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java @@ -88,7 +88,7 @@ public class FunctionDataTypeHTMLRepresentation extends HTMLDataTypeRepresentati GenericCallingConvention genericCallingConvention = functionDefinition.getGenericCallingConvention(); String modifier = genericCallingConvention != GenericCallingConvention.unknown - ? (" " + genericCallingConvention.name()) + ? (" " + genericCallingConvention.getDeclarationName()) : ""; return new TextLine( HTMLUtilities.friendlyEncodeHTML(returnDataType.getDisplayName()) + modifier); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java index e7adb329df..d91ff9750d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java @@ -50,7 +50,7 @@ import ghidra.app.plugin.core.datamgr.actions.CreateTypeDefDialog; import ghidra.app.plugin.core.datamgr.archive.Archive; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.tree.*; -import ghidra.app.plugin.core.function.EditFunctionSignatureDialog; +import ghidra.app.plugin.core.function.AbstractEditFunctionSignatureDialog; import ghidra.app.plugin.core.programtree.ProgramTreePlugin; import ghidra.app.services.ProgramManager; import ghidra.app.util.datatype.DataTypeSelectionEditor; @@ -695,11 +695,9 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe DataType dt = iter.next(); listTwo.add(dt); } - for (int i = 0; i < listOne.size(); i++) { - DataType dt = listOne.get(i); + for (DataType dt : listOne) { boolean found = false; - for (int j = 0; j < listTwo.size(); j++) { - DataType dt2 = listTwo.get(j); + for (DataType dt2 : listTwo) { if (dt.isEquivalent(dt2)) { found = true; break; @@ -807,8 +805,8 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe assertTrue(action.isEnabledForContext(treeContext)); performAction(action, treeContext, false); - EditFunctionSignatureDialog dialog = - waitForDialogComponent(EditFunctionSignatureDialog.class); + AbstractEditFunctionSignatureDialog dialog = + waitForDialogComponent(AbstractEditFunctionSignatureDialog.class); JTextField textField = (JTextField) getInstanceField("signatureField", dialog); setText(textField, newSignature); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/OverridePrototypeAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/OverridePrototypeAction.java index 39ddda0173..1d133909e2 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/OverridePrototypeAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/OverridePrototypeAction.java @@ -29,59 +29,12 @@ import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.pcode.*; import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.SourceType; import ghidra.util.*; import ghidra.util.exception.CancelledException; public class OverridePrototypeAction extends AbstractDecompilerAction { - public class ProtoOverrideDialog extends EditFunctionSignatureDialog { - private FunctionDefinition functionDefinition; - - public FunctionDefinition getFunctionDefinition() { - return functionDefinition; - } - - public ProtoOverrideDialog(PluginTool tool, Function func, String signature, String conv) { - super(tool, "Override Signature", func); - setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature")); - setSignature(signature); - setCallingConvention(conv); - } - - /** - * This method gets called when the user clicks on the OK Button. The base - * class calls this method. - */ - @Override - protected void okCallback() { - // only close the dialog if the user made valid changes - if (parseFunctionDefinition()) { - close(); - } - } - - private boolean parseFunctionDefinition() { - - functionDefinition = null; - - try { - functionDefinition = parseSignature(); - } - catch (CancelledException e) { - // ignore - } - - if (functionDefinition == null) { - return false; - } - - GenericCallingConvention convention = - GenericCallingConvention.guessFromName(getCallingConvention()); - functionDefinition.setGenericCallingConvention(convention); - return true; - } - } - public OverridePrototypeAction() { super("Override Signature"); setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature")); @@ -183,11 +136,30 @@ public class OverridePrototypeAction extends AbstractDecompilerAction { return null; } - private String generateSignature(PcodeOp op, String name) { + private String generateSignature(PcodeOp op, String name, Function calledfunc) { + + // TODO: If an override has already be placed-down it should probably be used + // for the initial signature. HighFunction does not make it easy to grab + // existing override prototype + + if (calledfunc != null) { + SourceType signatureSource = calledfunc.getSignatureSource(); + if (signatureSource == SourceType.DEFAULT || signatureSource == SourceType.ANALYSIS) { + calledfunc = null; // ignore + } + } + StringBuffer buf = new StringBuffer(); + Varnode vn = op.getOutput(); DataType dt = null; - if (vn != null) { + if (calledfunc != null) { + dt = calledfunc.getReturnType(); + if (Undefined.isUndefined(dt)) { + dt = null; + } + } + if (dt == null && vn != null) { dt = vn.getHigh().getDataType(); } if (dt != null) { @@ -198,26 +170,48 @@ public class OverridePrototypeAction extends AbstractDecompilerAction { } buf.append(' ').append(name).append('('); - for (int i = 1; i < op.getNumInputs(); ++i) { - vn = op.getInput(i); - dt = null; - if (vn != null) { - dt = vn.getHigh().getDataType(); - } - if (dt != null) { - buf.append(dt.getDisplayName()); - } - else { - buf.append("BAD"); - } - if (i != op.getNumInputs() - 1) { - buf.append(','); + + int index = 1; + if (calledfunc != null) { + for (Parameter p : calledfunc.getParameters()) { + String dtName = getInputDataTypeName(op, index, p.getDataType()); + if (index++ != 1) { + buf.append(", "); + } + buf.append(dtName); + if (p.getSource() != SourceType.DEFAULT) { + buf.append(' '); + buf.append(p.getName()); + } } } + + for (int i = index; i < op.getNumInputs(); ++i) { + if (i != 1) { + buf.append(", "); + } + buf.append(getInputDataTypeName(op, i, null)); + } + buf.append(')'); return buf.toString(); } + private String getInputDataTypeName(PcodeOp op, int inIndex, DataType preferredDt) { + if (preferredDt != null && !Undefined.isUndefined(preferredDt)) { + return preferredDt.getDisplayName(); + } + Varnode vn = op.getInput(inIndex); + DataType dt = null; + if (vn != null) { + dt = vn.getHigh().getDataType(); + } + if (dt != null) { + return dt.getDisplayName(); + } + return "BAD"; + } + @Override protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { Function function = context.getFunction(); @@ -257,9 +251,10 @@ public class OverridePrototypeAction extends AbstractDecompilerAction { conv = calledfunc.getCallingConventionName(); } - String signature = generateSignature(op, name); + String signature = generateSignature(op, name, calledfunc); PluginTool tool = context.getTool(); - ProtoOverrideDialog dialog = new ProtoOverrideDialog(tool, func, signature, conv); + ProtoOverrideDialog dialog = + new ProtoOverrideDialog(tool, calledfunc != null ? calledfunc : func, signature, conv); tool.showDialog(dialog); FunctionDefinition fdef = dialog.getFunctionDefinition(); if (fdef == null) { @@ -279,4 +274,71 @@ public class OverridePrototypeAction extends AbstractDecompilerAction { program.endTransaction(transaction, commit); } } + + /** + * ProtoOverrideDialog provides the ability to edit the + * function signature associated with a specific function definition override + * at a sub-function callsite. + * Use of this editor requires the presence of the tool-based datatype manager service. + */ + private class ProtoOverrideDialog extends EditFunctionSignatureDialog { + private FunctionDefinition functionDefinition; + private final String initialSignature; + private final String initialConvention; + + /** + * Construct signature override for called function + * @param tool active tool + * @param func function from which program access is achieved and supply of preferred + * datatypes when parsing signature + * @param signature initial prototype signature to be used + * @param conv initial calling convention + */ + public ProtoOverrideDialog(PluginTool tool, Function func, String signature, String conv) { + super(tool, "Override Signature", func, false, false, false); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature")); + this.initialSignature = signature; + this.initialConvention = conv; + } + + @Override + protected String getPrototypeString() { + return initialSignature; + } + + @Override + protected String getCallingConventionName() { + return initialConvention; + } + + @Override + protected boolean applyChanges() throws CancelledException { + return parseFunctionDefinition(); + } + + private boolean parseFunctionDefinition() { + + functionDefinition = null; + + try { + functionDefinition = parseSignature(); + } + catch (CancelledException e) { + // ignore + } + + if (functionDefinition == null) { + return false; + } + + GenericCallingConvention convention = + GenericCallingConvention.guessFromName(getCallingConvention()); + functionDefinition.setGenericCallingConvention(convention); + return true; + } + + public FunctionDefinition getFunctionDefinition() { + return functionDefinition; + } + } }