+ Apply Callee Function Signature and Data Types - Available when the selected
+ and matched decompiler tokens are function calls. Performs the same action as
+ Apply Function Signature
+ and Data Types, but acts on the callee functions.
Provided By: FunctionComparisonPlugin
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java
index b7ac2757df..591809872f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.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,8 +27,8 @@ import ghidra.util.Msg;
* comparison window
*/
public abstract class AbstractFunctionComparisonApplyAction extends DockingAction {
- protected static final String MENU_PARENT = "Apply From Other";
- protected static final String MENU_GROUP = "A0_Apply";
+ protected static final String MENU_PARENT = "Apply From Other Function";
+ protected static final String MENU_GROUP = "A0_ApplyFunction";
protected static final String HELP_TOPIC = "FunctionComparison";
/**
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java
new file mode 100644
index 0000000000..76f4dd0754
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java
@@ -0,0 +1,120 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import static ghidra.util.datastruct.Duo.Side.*;
+
+import ghidra.app.decompiler.ClangFuncNameToken;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.PcodeOp;
+import ghidra.util.Msg;
+
+/**
+ * Subclass of {@link AbstractMatchedTokensAction} for actions in a
+ * {@link DecompilerCodeComparisonPanel} that are available only when the matched tokens are
+ * function calls
+ */
+public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedTokensAction {
+ protected static final String MENU_GROUP = "A2_ApplyCallee";
+
+ /**
+ * Constructor
+ *
+ * @param actionName name of action
+ * @param owner owner of action
+ * @param diffPanel diff panel containing action
+ * @param disableOnReadOnly if true, action will be disabled for read-only programs
+ */
+ public AbstractMatchedCalleeTokensAction(String actionName, String owner,
+ DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
+ super(actionName, owner, diffPanel, disableOnReadOnly);
+ }
+
+ @Override
+ protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
+ TokenPair tokenPair = context.getTokenPair();
+
+ if (tokenPair == null) {
+ return false;
+ }
+ if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
+ return false;
+ }
+ PcodeOp leftOp = tokenPair.leftToken().getPcodeOp();
+ PcodeOp rightOp = tokenPair.rightToken().getPcodeOp();
+ if (leftOp == null || rightOp == null) {
+ return false;
+ }
+ if (leftOp.getOpcode() != PcodeOp.CALL || rightOp.getOpcode() != PcodeOp.CALL) {
+ return false;
+ }
+ return (tokenPair.leftToken() instanceof ClangFuncNameToken) &&
+ (tokenPair.rightToken() instanceof ClangFuncNameToken);
+ }
+
+ @Override
+ public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
+ DecompilerCodeComparisonPanel decompPanel = context.getCodeComparisonPanel();
+
+ TokenPair currentPair = context.getTokenPair();
+
+ ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
+ ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
+
+ Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
+ Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
+ if (leftFunction == null || rightFunction == null) {
+ return;
+ }
+
+ doCalleeActionPerformed(leftFunction, rightFunction);
+ }
+
+ /**
+ * Once function objects have been recovered from the callee tokens, perform an action
+ * @param leftFunction the callee function on the left side of the decompiler diff panel
+ * @param rightFunction the callee function on the right side of the decompiler diff panel
+ */
+ protected abstract void doCalleeActionPerformed(Function leftFunction, Function rightFunction);
+
+ private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {
+ Address callTarget = funcToken.getPcodeOp().getInput(0).getAddress();
+ Function func = program.getFunctionManager().getFunctionAt(callTarget);
+ if (func == null) {
+ Msg.showWarn(this, null, "Unable to Compare Callees",
+ "Can't compare callees - null Function for " + funcToken.getText());
+ return null;
+ }
+ if (func.isExternal()) {
+ Msg.showWarn(this, null, "Unable to Compare Callees",
+ "Can't compare callees - " + func.getName() + " is external");
+ return null;
+ }
+ if (!func.isThunk()) {
+ return func;
+ }
+ func = func.getThunkedFunction(true);
+ if (func.isExternal()) {
+ Msg.showWarn(this, null, "Unable to Compare",
+ "Can't compare callees - " + func.getName() + " is external");
+ return null;
+ }
+ return func;
+
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java
index b15152cc34..0788c7366b 100644
--- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java
@@ -15,24 +15,15 @@
*/
package ghidra.features.codecompare.decompile;
-import static ghidra.util.datastruct.Duo.Side.*;
-
-import java.util.Iterator;
-import java.util.List;
-
import docking.ActionContext;
import docking.action.DockingAction;
-import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.DecompilerLocation;
-import ghidra.app.decompiler.component.DecompilerPanel;
-import ghidra.features.codecompare.graphanalysis.TokenBin;
-import ghidra.program.model.listing.Program;
-import ghidra.util.datastruct.Duo.Side;
/**
* This is a base class for actions in a {@link DecompilerCodeComparisonPanel}
*/
public abstract class AbstractMatchedTokensAction extends DockingAction {
+ protected static final String MENU_PARENT = "Apply From Other Function";
+ protected static final String HELP_TOPIC = "FunctionComparison";
protected DecompilerCodeComparisonPanel diffPanel;
protected boolean disableOnReadOnly;
@@ -52,83 +43,43 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
this.disableOnReadOnly = disableOnReadOnly;
}
- /**
- * Determines whether the action should be enable for a pair of
- * matching tokens.
- *
- * @param tokenPair tokens
- * @return true if action should be enabled
- */
- protected abstract boolean enabledForTokens(TokenPair tokenPair);
+ @Override
+ public void actionPerformed(ActionContext context) {
+ if (!(context instanceof DualDecompilerActionContext compareContext)) {
+ return;
+ }
+
+ dualDecompilerActionPerformed(compareContext);
+ }
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DualDecompilerActionContext compareContext)) {
return false;
}
- DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
if (disableOnReadOnly) {
- //get the program corresponding to the panel with focus
- Side focusedSide = decompPanel.getActiveSide();
- Program program = decompPanel.getProgram(focusedSide);
- if (program == null) {
- return false; //panel initializing; don't enable action
- }
- if (!program.canSave()) {
- return false; //program is read-only, don't enable action
+ if (compareContext.isActiveProgramReadOnly()) {
+ return false; // program is read-only, don't enable action
}
}
- TokenPair currentPair = getCurrentTokenPair(decompPanel);
- return enabledForTokens(currentPair);
-
+ return isEnabledForDualDecompilerContext(compareContext);
}
/**
- * Returns a {@link TokenPair} consisting of the token under the cursor in the focused
- * decompiler panel and its counterpart in the other panel.
- *
- * @param decompPanel decomp panel
- * @return matching tokens (or null if no match)
+ * Subclasses return true if they are enabled for the given context
+ *
+ * @param context the context
+ * @return true if enabled
*/
- protected TokenPair getCurrentTokenPair(
- DecompilerCodeComparisonPanel decompPanel) {
+ protected abstract boolean isEnabledForDualDecompilerContext(
+ DualDecompilerActionContext context);
- DecompilerPanel focusedPanel = decompPanel.getActiveDisplay().getDecompilerPanel();
-
- if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
- return null;
- }
-
- ClangToken focusedToken = focusedLocation.getToken();
- if (focusedToken == null) {
- return null;
- }
- List tokenBin = diffPanel.getHighBins();
- if (tokenBin == null) {
- return null;
- }
- TokenBin containingBin = TokenBin.getBinContainingToken(tokenBin, focusedToken);
- if (containingBin == null) {
- return null;
- }
- TokenBin matchedBin = containingBin.getMatch();
- if (matchedBin == null) {
- return null;
- }
- //loop over the tokens in the matching bin and return the first one in the same
- //class as focusedToken
- Iterator tokenIter = matchedBin.iterator();
- while (tokenIter.hasNext()) {
- ClangToken currentMatch = tokenIter.next();
- if (currentMatch.getClass().equals(focusedToken.getClass())) {
- return decompPanel.getActiveSide() == LEFT
- ? new TokenPair(focusedToken, currentMatch)
- : new TokenPair(currentMatch, focusedToken);
- }
- }
- return null;
- }
+ /**
+ * Subclasses will perform their work in this method
+ * @param context the context
+ */
+ protected abstract void dualDecompilerActionPerformed(DualDecompilerActionContext context);
}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java
new file mode 100644
index 0000000000..cd96648da4
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java
@@ -0,0 +1,72 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.FunctionUtility;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * An action for transferring the 'skeleton' function signature between matched callee tokens.
+ */
+public class ApplyCalleeEmptySignatureFromMatchedTokensAction
+ extends AbstractMatchedCalleeTokensAction {
+
+ private PluginTool tool;
+ public static final String ACTION_NAME = "Function Comparison Apply Callee Signature";
+
+ /**
+ * Construtor
+ * @param diffPanel diff panel
+ * @param tool tool
+ */
+ public ApplyCalleeEmptySignatureFromMatchedTokensAction(
+ DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Callee Signature" }, null,
+ MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
+
+ Side activeSide = diffPanel.getActiveSide();
+
+ Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
+ Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;
+
+ Program activeProgram = activeFunction.getProgram();
+
+ try {
+ activeProgram.withTransaction("Code Comparison Transfer Callee Signature",
+ () -> FunctionUtility.applySignature(activeFunction, otherFunction, true, null));
+ }
+ catch (Exception e) {
+ Msg.showError(this, tool.getToolFrame(), "Failed to Apply Callee Signature",
+ e.getMessage());
+ }
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java
new file mode 100644
index 0000000000..77daf33b5c
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java
@@ -0,0 +1,71 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.FunctionUtility;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * An action for transferring just the function name and namespace between matched callee tokens.
+ */
+public class ApplyCalleeFunctionNameFromMatchedTokensAction
+ extends AbstractMatchedCalleeTokensAction {
+
+ private PluginTool tool;
+ public static final String ACTION_NAME = "Function Comparison Apply Callee Name";
+
+ /**
+ * Construtor
+ * @param diffPanel diff panel
+ * @param tool tool
+ */
+ public ApplyCalleeFunctionNameFromMatchedTokensAction(
+ DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Callee Name" }, null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
+
+ Side activeSide = diffPanel.getActiveSide();
+
+ Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
+ Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;
+
+ Program activeProgram = activeFunction.getProgram();
+
+ try {
+ activeProgram.withTransaction("Code Comparison Transfer Callee Function Name",
+ () -> FunctionUtility.applyNameAndNamespace(activeFunction, otherFunction));
+ }
+ catch (Exception e) {
+ Msg.showError(this, tool.getToolFrame(), "Failed to Apply Callee Function Name",
+ e.getMessage());
+ }
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java
new file mode 100644
index 0000000000..c2903ca6c9
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java
@@ -0,0 +1,73 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.FunctionUtility;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * An action for transferring the full function signature between matched callee tokens
+ */
+public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction
+ extends AbstractMatchedCalleeTokensAction {
+ private PluginTool tool;
+ public static final String ACTION_NAME =
+ "Function Comparison Apply Callee Signature And Datatypes";
+
+ /**
+ * Construtor
+ * @param diffPanel diff panel
+ * @param tool tool
+ */
+ public ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(
+ DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Callee Signature and Data Types" },
+ null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
+
+ Side activeSide = diffPanel.getActiveSide();
+
+ Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
+ Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;
+
+ Program activeProgram = activeFunction.getProgram();
+
+ try {
+ activeProgram.withTransaction(
+ "Code Comparison Transfer Callee Signature and Data Types",
+ () -> FunctionUtility.applySignature(activeFunction, otherFunction, false, null));
+ }
+ catch (Exception e) {
+ Msg.showError(this, tool.getToolFrame(),
+ "Failed to Apply Callee Signature and Data Types", e.getMessage());
+ }
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java
new file mode 100644
index 0000000000..f7abeadcee
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java
@@ -0,0 +1,121 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.app.decompiler.ClangVariableToken;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.HighFunctionDBUtil;
+import ghidra.program.model.pcode.HighSymbol;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.util.DataTypeCleaner;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+import ghidra.util.exception.*;
+
+public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatchedTokensAction {
+ private PluginTool tool;
+ public static final String ACTION_NAME = "Function Comparison Apply Variable Skeleton Type";
+ private static final String MENU_GROUP = "A1_ApplyVariable";
+
+ public ApplyEmptyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
+ PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Variable Skeleton Type" }, null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
+ TokenPair tokenPair = context.getTokenPair();
+
+ if (tokenPair == null) {
+ return false;
+ }
+
+ if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
+ return false;
+ }
+
+ if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
+ !(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
+ return false;
+ }
+
+ HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
+ HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
+
+ return leftSymbol != null && rightSymbol != null;
+ }
+
+ @Override
+ protected void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
+ TokenPair currentPair = context.getTokenPair();
+
+ Side activeSide = diffPanel.getActiveSide();
+
+ ClangVariableToken activeToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
+ : (ClangVariableToken) currentPair.rightToken();
+ ClangVariableToken otherToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
+ : (ClangVariableToken) currentPair.leftToken();
+
+ HighSymbol activeHighSymbol =
+ activeToken.getHighSymbol(context.getHighFunction(activeSide));
+ HighSymbol otherHighSymbol =
+ otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
+
+ Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
+ Program activeProgram = activeFunction.getProgram();
+
+ DataType dt = otherHighSymbol.getDataType();
+
+ try {
+ activeProgram.withTransaction("Code Comparison Transfer Local Skeleton Type", () -> {
+ DataTypeCleaner dtCleaner =
+ new DataTypeCleaner(activeProgram.getDataTypeManager(), true);
+ try {
+ DataType resolvedDt = dtCleaner.clean(dt);
+ resolvedDt = activeProgram.getDataTypeManager().resolve(resolvedDt, null);
+
+ HighFunctionDBUtil.updateDBVariable(activeHighSymbol, null, resolvedDt,
+ SourceType.IMPORTED);
+ }
+ finally {
+ dtCleaner.close();
+ }
+ });
+ }
+ catch (InvalidInputException e) {
+ Msg.showError(this, tool.getToolFrame(), "Skeleton Type Transfer Failed",
+ "Could not bring cleaned type " + dt.getName() + " to " + activeProgram.getName());
+ }
+ catch (UsrException e) {
+ throw new AssertException("Unexpected exception", e);
+ }
+
+ }
+
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java
new file mode 100644
index 0000000000..7aa54371b1
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java
@@ -0,0 +1,127 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.app.cmd.label.RenameLabelCmd;
+import ghidra.app.decompiler.ClangVariableToken;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.HighCodeSymbol;
+import ghidra.program.model.pcode.HighSymbol;
+import ghidra.program.model.symbol.*;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * An action for transferring the name of a global variable between matched tokens.
+ */
+public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedTokensAction {
+ private PluginTool tool;
+ public static final String ACTION_NAME = "Function Comparison Apply Global Variable Name";
+ private static final String MENU_GROUP = "A1_ApplyVariable";
+
+ /**
+ * Construtor
+ * @param diffPanel diff panel
+ * @param tool tool
+ */
+ public ApplyGlobalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
+ PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Variable Name" }, null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
+ TokenPair tokenPair = context.getTokenPair();
+
+ if (tokenPair == null) {
+ return false;
+ }
+
+ if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
+ return false;
+ }
+
+ if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
+ !(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
+ return false;
+ }
+
+ HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
+ HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
+
+ if (leftSymbol == null || rightSymbol == null) {
+ return false;
+ }
+
+ return leftSymbol.isGlobal() && rightSymbol.isGlobal();
+ }
+
+ @Override
+ public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
+ TokenPair currentPair = context.getTokenPair();
+
+ Side activeSide = diffPanel.getActiveSide();
+ ClangVariableToken activeToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
+ : (ClangVariableToken) currentPair.rightToken();
+ ClangVariableToken otherToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
+ : (ClangVariableToken) currentPair.leftToken();
+
+ HighSymbol activeHighSymbol =
+ activeToken.getHighSymbol(context.getHighFunction(activeSide));
+ HighSymbol otherHighSymbol =
+ otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
+
+ Program activeProgram = context.getCodeComparisonPanel().getProgram(activeSide);
+
+ Symbol activeSymbol = null;
+ if (activeHighSymbol instanceof HighCodeSymbol activeCodeSymbol) {
+ activeSymbol = activeCodeSymbol.getCodeSymbol();
+ if (activeSymbol == null) {
+ Address addr = activeCodeSymbol.getStorage().getMinAddress();
+ SymbolTable symbolTable = activeProgram.getSymbolTable();
+ activeSymbol = symbolTable.getPrimarySymbol(addr);
+ }
+ }
+ if (activeSymbol == null) {
+ Msg.showError(this, tool.getToolFrame(), "Name transfer failed",
+ "Failed to find memory storage for target global");
+ return;
+ }
+
+ RenameLabelCmd cmd = new RenameLabelCmd(activeSymbol, otherHighSymbol.getName(),
+ otherHighSymbol.getNamespace(), SourceType.IMPORTED);
+
+ activeProgram.withTransaction("Code Comparison Apply Global Variable Name",
+ () -> cmd.applyTo(activeProgram));
+
+ if (!cmd.getStatusMsg().isEmpty()) {
+ Msg.showError(this, tool.getToolFrame(), "Name transfer failed", cmd.getStatusMsg());
+ }
+ }
+
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java
new file mode 100644
index 0000000000..9397bfc4be
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java
@@ -0,0 +1,117 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.app.decompiler.ClangVariableToken;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.HighFunctionDBUtil;
+import ghidra.program.model.pcode.HighSymbol;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+import ghidra.util.exception.*;
+
+/**
+ * An action for transferring the name of a local variable between matched tokens.
+ */
+public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokensAction {
+ private PluginTool tool;
+ public static final String ACTION_NAME = "Function Comparison Apply Local Variable Name";
+ private static final String MENU_GROUP = "A1_ApplyVariable";
+
+ /**
+ * Construtor
+ * @param diffPanel diff panel
+ * @param tool tool
+ */
+ public ApplyLocalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
+ PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Variable Name" }, null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
+ TokenPair tokenPair = context.getTokenPair();
+
+ if (tokenPair == null) {
+ return false;
+ }
+
+ if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
+ return false;
+ }
+
+ if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
+ !(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
+ return false;
+ }
+ HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
+ HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
+
+ if (leftSymbol == null || rightSymbol == null) {
+ return false;
+ }
+
+ return !leftSymbol.isGlobal() && !rightSymbol.isGlobal();
+ }
+
+ @Override
+ public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
+ TokenPair currentPair = context.getTokenPair();
+
+ Side activeSide = diffPanel.getActiveSide();
+
+ ClangVariableToken activeToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
+ : (ClangVariableToken) currentPair.rightToken();
+ ClangVariableToken otherToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
+ : (ClangVariableToken) currentPair.leftToken();
+
+ HighSymbol activeHighSymbol =
+ activeToken.getHighSymbol(context.getHighFunction(activeSide));
+ HighSymbol otherHighSymbol =
+ otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
+
+ Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
+ Program activeProgram = activeFunction.getProgram();
+
+ try {
+ activeProgram.withTransaction("Code Comparison Apply Local Variable Name",
+ () -> HighFunctionDBUtil.updateDBVariable(activeHighSymbol,
+ otherHighSymbol.getName(),
+ null, SourceType.IMPORTED));
+ }
+ catch (DuplicateNameException e) {
+ Msg.showError(this, tool.getToolFrame(), "Duplicate Name",
+ "Name " + otherHighSymbol.getName() + " already exists in function " +
+ activeFunction.getName());
+ }
+ catch (UsrException e) {
+ throw new AssertException("Unexpected exception", e);
+ }
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java
new file mode 100644
index 0000000000..4adeed1944
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java
@@ -0,0 +1,117 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import docking.action.MenuData;
+import ghidra.app.decompiler.ClangVariableToken;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.HighFunctionDBUtil;
+import ghidra.program.model.pcode.HighSymbol;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Duo.Side;
+import ghidra.util.exception.*;
+
+/**
+ * An action for transferring the type of a local or global variable between matched tokens.
+ */
+public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTokensAction {
+ private PluginTool tool;
+ public static final String ACTION_NAME = "Function Comparison Apply Variable Type";
+ private static final String MENU_GROUP = "A1_ApplyVariable";
+
+ /**
+ * Construtor
+ * @param diffPanel diff panel
+ * @param tool tool
+ */
+ public ApplyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
+ PluginTool tool) {
+ super(ACTION_NAME, tool.getName(), diffPanel, true);
+ this.tool = tool;
+
+ MenuData menuData =
+ new MenuData(new String[] { MENU_PARENT, "Variable Type" }, null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
+ }
+
+ @Override
+ protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
+ TokenPair tokenPair = context.getTokenPair();
+
+ if (tokenPair == null) {
+ return false;
+ }
+
+ if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
+ return false;
+ }
+
+ if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
+ !(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
+ return false;
+ }
+
+ HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
+ HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
+
+ return leftSymbol != null && rightSymbol != null;
+ }
+
+ @Override
+ public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
+ TokenPair currentPair = context.getTokenPair();
+
+ Side activeSide = diffPanel.getActiveSide();
+
+ ClangVariableToken activeToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
+ : (ClangVariableToken) currentPair.rightToken();
+ ClangVariableToken otherToken =
+ activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
+ : (ClangVariableToken) currentPair.leftToken();
+
+ HighSymbol activeHighSymbol =
+ activeToken.getHighSymbol(context.getHighFunction(activeSide));
+ HighSymbol otherHighSymbol =
+ otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
+
+ Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
+ Program activeProgram = activeFunction.getProgram();
+
+ DataType dt = otherHighSymbol.getDataType();
+
+ try {
+ activeProgram.withTransaction("Code Comparison Transfer Local Type", () -> {
+ DataType resolvedDt = activeProgram.getDataTypeManager().resolve(dt, null);
+ HighFunctionDBUtil.updateDBVariable(activeHighSymbol, null, resolvedDt,
+ SourceType.IMPORTED);
+ });
+ }
+ catch (InvalidInputException e) {
+ Msg.showError(this, tool.getToolFrame(), "Type Transfer Failed",
+ "Could not bring type " + dt.getName() + " to " + activeProgram.getName());
+ }
+ catch (UsrException e) {
+ throw new AssertException("Unexpected exception", e);
+ }
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java
index 127d862c9c..fb2d999a12 100644
--- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.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,17 +15,10 @@
*/
package ghidra.features.codecompare.decompile;
-import static ghidra.util.datastruct.Duo.Side.*;
-
-import docking.ActionContext;
import docking.action.MenuData;
-import ghidra.app.decompiler.ClangFuncNameToken;
import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.PluginTool;
-import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
-import ghidra.program.model.listing.Program;
-import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
@@ -33,11 +26,9 @@ import ghidra.util.Msg;
* An action for bringing up a side-by-side function comparison of callees with matching
* tokens.
*/
-public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAction {
+public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedCalleeTokensAction {
private PluginTool tool;
private static final String ACTION_NAME = "Compare Matching Callees";
- private static final String MENU_GROUP = "A1_Compare";
- private static final String HELP_TOPIC = "FunctionComparison";
/**
* Constructor
@@ -48,58 +39,14 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, false);
this.tool = tool;
- FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
- if (service != null) {
- MenuData menuData = new MenuData(new String[] { ACTION_NAME }, null, MENU_GROUP);
- setPopupMenuData(menuData);
- setEnabled(true);
- setHelpLocation(new HelpLocation(HELP_TOPIC, "Compare Matching Callees"));
- }
+
+ MenuData menuData = new MenuData(new String[] { ACTION_NAME }, null, MENU_GROUP);
+ setPopupMenuData(menuData);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, "Compare Matching Callees"));
}
@Override
- protected boolean enabledForTokens(TokenPair tokenPair) {
- if (tokenPair == null) {
- return false;
- }
- if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
- return false;
- }
- PcodeOp leftOp = tokenPair.leftToken().getPcodeOp();
- PcodeOp rightOp = tokenPair.rightToken().getPcodeOp();
- if (leftOp == null || rightOp == null) {
- return false;
- }
- if (leftOp.getOpcode() != PcodeOp.CALL || rightOp.getOpcode() != PcodeOp.CALL) {
- return false;
- }
- return (tokenPair.leftToken() instanceof ClangFuncNameToken) &&
- (tokenPair.rightToken() instanceof ClangFuncNameToken);
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- if (!(context instanceof DualDecompilerActionContext compareContext)) {
- return;
- }
-
- DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
-
- TokenPair currentPair = getCurrentTokenPair(decompPanel);
- if (currentPair == null || currentPair.leftToken() == null ||
- currentPair.rightToken() == null) {
- return;
- }
-
- ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
- ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
-
- Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
- Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
- if (leftFunction == null || rightFunction == null) {
- return;
- }
-
+ protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
if (service == null) {
Msg.error(this, "Function Comparison Service not found!");
@@ -108,30 +55,4 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
service.createComparison(leftFunction, rightFunction);
}
- private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {
- Address callTarget = funcToken.getPcodeOp().getInput(0).getAddress();
- Function func = program.getFunctionManager().getFunctionAt(callTarget);
- if (func == null) {
- Msg.showWarn(this, null, "Unable to Compare Callees",
- "Can't compare callees - null Function for " + funcToken.getText());
- return null;
- }
- if (func.isExternal()) {
- Msg.showWarn(this, null, "Unable to Compare Callees",
- "Can't compare callees - " + func.getName() + " is external");
- return null;
- }
- if (!func.isThunk()) {
- return func;
- }
- func = func.getThunkedFunction(true);
- if (func.isExternal()) {
- Msg.showWarn(this, null, "Unable to Compare",
- "Can't compare callees - " + func.getName() + " is external");
- return null;
- }
- return func;
-
- }
-
}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java
index b73ab6baa0..1734223942 100644
--- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java
@@ -182,6 +182,13 @@ public class DecompilerCodeComparisonPanel
actions.add(new DecompilerCodeComparisonOptionsAction());
actions.add(toggleExactConstantMatchingAction);
actions.add(new CompareFuncsFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyLocalNameFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyGlobalNameFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyVariableTypeFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyEmptyVariableTypeFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyCalleeFunctionNameFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyCalleeEmptySignatureFromMatchedTokensAction(this, tool));
+ actions.add(new ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(this, tool));
}
private void decompileDataSet(Side side, DecompileData dcompileData) {
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java
index 4c1e5b22c8..675d0b15b1 100644
--- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.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,11 +15,22 @@
*/
package ghidra.features.codecompare.decompile;
+import static ghidra.util.datastruct.Duo.Side.*;
+
import java.awt.Component;
+import java.util.Iterator;
+import java.util.List;
import docking.ComponentProvider;
import ghidra.app.context.RestrictedAddressSetContext;
+import ghidra.app.decompiler.ClangToken;
+import ghidra.app.decompiler.DecompilerLocation;
+import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.features.base.codecompare.panel.CodeComparisonActionContext;
+import ghidra.features.codecompare.graphanalysis.TokenBin;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.HighFunction;
+import ghidra.util.datastruct.Duo.Side;
/**
* Action context for a dual decompiler panel.
@@ -28,6 +39,8 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
implements RestrictedAddressSetContext {
private DecompilerCodeComparisonPanel decompilerComparisonPanel = null;
+ private TokenPair tokenPair;
+ private boolean overrideReadOnly = false;
/**
* Creates an action context for a dual decompiler panel.
@@ -38,7 +51,47 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
public DualDecompilerActionContext(ComponentProvider provider,
DecompilerCodeComparisonPanel panel, Component source) {
super(provider, panel, source);
- this.decompilerComparisonPanel = panel;
+ decompilerComparisonPanel = panel;
+ tokenPair = computeTokenPair();
+ }
+
+ private TokenPair computeTokenPair() {
+ DecompilerPanel focusedPanel =
+ decompilerComparisonPanel.getActiveDisplay().getDecompilerPanel();
+
+ if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
+ return null;
+ }
+
+ ClangToken focusedToken = focusedLocation.getToken();
+ if (focusedToken == null) {
+ return null;
+ }
+ List tokenBin = decompilerComparisonPanel.getHighBins();
+ if (tokenBin == null) {
+ return null;
+ }
+ TokenBin containingBin = TokenBin.getBinContainingToken(tokenBin, focusedToken);
+ if (containingBin == null) {
+ return null;
+ }
+ TokenBin matchedBin = containingBin.getMatch();
+ if (matchedBin == null) {
+ return null;
+ }
+
+ //loop over the tokens in the matching bin and return the first one in the same
+ //class as focusedToken
+ Iterator tokenIter = matchedBin.iterator();
+ while (tokenIter.hasNext()) {
+ ClangToken currentMatch = tokenIter.next();
+ if (currentMatch.getClass().equals(focusedToken.getClass())) {
+ return decompilerComparisonPanel.getActiveSide() == LEFT
+ ? new TokenPair(focusedToken, currentMatch)
+ : new TokenPair(currentMatch, focusedToken);
+ }
+ }
+ return null;
}
/**
@@ -49,4 +102,55 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
public DecompilerCodeComparisonPanel getCodeComparisonPanel() {
return decompilerComparisonPanel;
}
+
+ /**
+ * Returns the {@link HighFunction} being viewed on the given side by the decompiler panel that
+ * generated this context
+ * @param side the side of the comparison to retrieve the high function for
+ * @return the high function on the given side of the comparison panel that generated this
+ * context
+ */
+ public HighFunction getHighFunction(Side side) {
+ return decompilerComparisonPanel.getDecompilerPanel(side).getController().getHighFunction();
+ }
+
+ /**
+ * Returns the {@link TokenPair} currently selected in the diff view, if any.
+ * @return the token pair selected when this context was generated
+ */
+ public TokenPair getTokenPair() {
+ return tokenPair;
+ }
+
+ /**
+ * Set whether this context will bypass a check to the actual state of the active program
+ * when resolving {@link #isActiveProgramReadOnly}. Used by tests.
+ * @param overrideReadOnly true if this context should bypass an
+ * {@link #isActiveProgramReadOnly} by always returning false
+ */
+ void setOverrideReadOnly(boolean overrideReadOnly) {
+ this.overrideReadOnly = overrideReadOnly;
+ }
+
+ /**
+ * Check if the program associated with the focused window in the dual decompiler view is
+ * read only. Always false if read only override was set to true with a call to
+ * {@link #setOverrideReadOnly}
+ * @return true if the active program is read only, always false if override is set to true
+ */
+ public boolean isActiveProgramReadOnly() {
+ if (overrideReadOnly) {
+ return false;
+ }
+
+ Program activeProgram =
+ decompilerComparisonPanel.getProgram(decompilerComparisonPanel.getActiveSide());
+
+ if (activeProgram == null) {
+ return true;
+ }
+
+ return activeProgram.getDomainFile().isReadOnly();
+ }
+
}
diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java
new file mode 100644
index 0000000000..2a8cb73968
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java
@@ -0,0 +1,117 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import java.awt.Point;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+
+import docking.widgets.fieldpanel.FieldPanel;
+import docking.widgets.fieldpanel.support.FieldLocation;
+import ghidra.app.decompiler.ClangToken;
+import ghidra.app.decompiler.component.ClangTextField;
+import ghidra.app.decompiler.component.DecompilerPanel;
+import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
+import ghidra.app.plugin.core.function.FunctionPlugin;
+import ghidra.features.base.codecompare.panel.CodeComparisonPanel;
+import ghidra.features.codecompare.plugin.FunctionComparisonPlugin;
+import ghidra.features.codecompare.plugin.FunctionComparisonProvider;
+import ghidra.program.model.listing.Function;
+import ghidra.test.AbstractGhidraHeadedIntegrationTest;
+import ghidra.test.TestEnv;
+import ghidra.util.datastruct.Duo.Side;
+
+public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedIntegrationTest {
+ protected TestEnv env;
+ protected FunctionComparisonPlugin fcPlugin;
+ protected FunctionPlugin fPlugin;
+ protected CodeBrowserPlugin cbPlugin;
+
+ @Before
+ public void setUp() throws Exception {
+ env = new TestEnv();
+ fcPlugin = env.addPlugin(FunctionComparisonPlugin.class);
+ fPlugin = env.addPlugin(FunctionPlugin.class);
+ cbPlugin = env.addPlugin(CodeBrowserPlugin.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ env.dispose();
+ }
+
+ protected FunctionComparisonProvider compareFunctions(Set functions) {
+ runSwing(() -> fcPlugin.createComparison(functions));
+ waitForSwing();
+ return waitForComponentProvider(FunctionComparisonProvider.class);
+ }
+
+ protected DecompilerCodeComparisonPanel findDecompilerPanel(
+ FunctionComparisonProvider provider) {
+ for (CodeComparisonPanel panel : provider.getComponent().getComparisonPanels()) {
+ if (panel instanceof DecompilerCodeComparisonPanel decompPanel) {
+ return decompPanel;
+ }
+ }
+
+ return null;
+ }
+
+ protected void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) {
+ runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName()));
+ waitForSwing();
+ }
+
+ protected void waitForDecompile(DecompilerCodeComparisonPanel panel) {
+ waitForSwing();
+ waitForCondition(() -> !panel.isBusy());
+ waitForSwing();
+ }
+
+ protected DecompilerPanel getDecompSide(DecompilerCodeComparisonPanel panel, Side side) {
+ CDisplay sideDisplay = side == Side.LEFT ? panel.getLeftPanel() : panel.getRightPanel();
+ return sideDisplay.getDecompilerPanel();
+ }
+
+ // 1-indexed lines
+ protected ClangToken setDecompLocation(DecompilerCodeComparisonPanel comparePanel, Side side,
+ int line, int charPos) {
+ DecompilerPanel panel = getDecompSide(comparePanel, side);
+ FieldPanel fp = panel.getFieldPanel();
+ FieldLocation loc = new FieldLocation(line - 1, 0, 0, charPos); // 0-indexed lines
+
+ fp.scrollTo(loc);
+
+ Point p = fp.getPointForLocation(loc);
+
+ click(fp, p, 1, true);
+
+ waitForSwing();
+
+ return getCurrentToken(comparePanel, side);
+ }
+
+ // Get the token under the cursor at the given side
+ protected ClangToken getCurrentToken(DecompilerCodeComparisonPanel comparePanel, Side side) {
+ DecompilerPanel panel = getDecompSide(comparePanel, side);
+ FieldLocation loc = panel.getCursorPosition();
+ int lineNumber = loc.getIndex().intValue();
+ ClangTextField field = (ClangTextField) panel.getFields().get(lineNumber);
+ return field.getToken(loc);
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java
new file mode 100644
index 0000000000..93cf92cca3
--- /dev/null
+++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java
@@ -0,0 +1,793 @@
+/* ###
+ * 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.features.codecompare.decompile;
+
+import static org.junit.Assert.*;
+
+import java.util.Set;
+
+import org.junit.*;
+
+import docking.ActionContext;
+import docking.action.DockingActionIf;
+import ghidra.app.decompiler.ClangToken;
+import ghidra.features.codecompare.plugin.FunctionComparisonProvider;
+import ghidra.program.model.data.*;
+import ghidra.program.model.listing.*;
+import ghidra.util.datastruct.Duo.Side;
+
+public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
+ private FunctionComparisonProvider provider = null;
+
+ private Program progDemangler24Debug;
+ private Function funcDemangler24DebugMain;
+ private Function funcDemangler24DebugCplusDemangle;
+ private static final long DEMANGLER_24_DEBUG_MAIN_OFFSET = 0x414835;
+ private static final long DEMANGLER_24_DEBUG_CPLUS_DEMANGLE_OFFSET = 0x40c01b;
+ private static final long DEMANGLER_24_DEBUG_INTERNAL_CPLUS_DEMANGLE_OFFSET = 0x40c987;
+ private static final String DEMANGLER_24_DEBUG_PROG_NAME =
+ "CodeCompare/demangler_gnu_v2_24_fulldebug";
+
+ private Program progDemangler24Stripped;
+ private Function funcDemangler24StrippedMain;
+ private Function funcDemangler24StrippedCplusDemangle;
+ private static final long DEMANGLER_24_STRIPPED_MAIN_OFFSET = 0x414835;
+ private static final long DEMANGLER_24_STRIPPED_CPLUS_DEMANGLE_OFFSET = 0x40c01b;
+ private static final long DEMANGLER_24_STRIPPED_INTERNAL_CPLUS_DEMANGLE_OFFSET = 0x40c987;
+ private static final String DEMANGLER_24_STRIPPED_PROG_NAME =
+ "CodeCompare/demangler_gnu_v2_24_stripped";
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ progDemangler24Debug = env.getProgram(DEMANGLER_24_DEBUG_PROG_NAME);
+ progDemangler24Stripped = env.getProgram(DEMANGLER_24_STRIPPED_PROG_NAME);
+
+ assertNotNull(progDemangler24Debug);
+ assertNotNull(progDemangler24Stripped);
+
+ funcDemangler24DebugMain =
+ getFunctionFromOffset(progDemangler24Debug, DEMANGLER_24_DEBUG_MAIN_OFFSET);
+ funcDemangler24DebugCplusDemangle =
+ getFunctionFromOffset(progDemangler24Debug, DEMANGLER_24_DEBUG_CPLUS_DEMANGLE_OFFSET);
+ funcDemangler24StrippedMain =
+ getFunctionFromOffset(progDemangler24Stripped, DEMANGLER_24_STRIPPED_MAIN_OFFSET);
+ funcDemangler24StrippedCplusDemangle =
+ getFunctionFromOffset(progDemangler24Stripped,
+ DEMANGLER_24_STRIPPED_CPLUS_DEMANGLE_OFFSET);
+
+ assertNotNull(funcDemangler24DebugMain);
+ assertNotNull(funcDemangler24DebugCplusDemangle);
+ assertNotNull(funcDemangler24StrippedMain);
+ assertNotNull(funcDemangler24StrippedCplusDemangle);
+
+ showTool(fcPlugin.getTool());
+ env.open(progDemangler24Debug);
+ env.open(progDemangler24Stripped);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /*
+ * Comparisons of these functions are used in the next few tests
+
+
+ Decomp of 'main' in 'demangler_gnu_v2_24_fulldebug':
+ 1|
+ 2| int main(int argc,char **argv)
+ 3|
+ 4| {
+ 5| char *pcVar1;
+ 6| char **argv_local;
+ 7| int argc_local;
+ 8| char *options;
+ 9| char *demangler;
+ 10| int skip_first;
+ 11| int i;
+ 12| char *valid_symbols;
+ 13| int c;
+ 14|
+ 15| demangler = (char *)0x0;
+ 16| options = (char *)0x0;
+ 17| program_name = *argv;
+ 18| strip_underscore = prepends_underscore;
+ 19| argv_local = argv;
+ 20| argc_local = argc;
+ 21| expandargv(&argc_local,&argv_local);
+ ...
+
+
+ Decomp of 'FUN_00414835' (main) in 'demangler_gnu_v2_41_stripped':
+ 1|
+ 2| undefined8 FUN_00414835(int param_1,undefined8 *param_2)
+ 3|
+ 4| {
+ 5| char *pcVar1;
+ 6| undefined8 *local_48;
+ 7| int local_3c [3];
+ 8| undefined8 local_30;
+ 9| undefined8 local_28;
+ 10| int local_20;
+ 11| int local_1c;
+ 12| char *local_18;
+ 13| uint local_c;
+ 14|
+ 15| local_28 = 0;
+ 16| local_30 = 0;
+ 17| DAT_004252e0 = *param_2;
+ 18| DAT_0041d220 = DAT_00425240;
+ 19| local_48 = param_2;
+ 20| local_3c[0] = param_1;
+ 21| FUN_00401a2d(local_3c,&local_48);
+ ...
+
+
+ Decomp of 'FUN_0040c01b' (cplus_demangle) in 'demangler_gnu_v2_24_stripped':
+ 1|
+ 2| long FUN_0040c01b(undefined8 param_1,uint param_2)
+ 3|
+ 4| {
+ 5| long lVar1;
+ 6| uint local_88 [30];
+ 7| long local_10;
+ 8|
+ 9| if (DAT_0041d150 == 0xffffffff) {
+ 10| local_10 = FUN_0040b419(param_1);
+ 11| }
+ 12| else {
+ 13| memset(local_88,0,0x70);
+ 14| local_88[0] = param_2;
+ 15| if ((param_2 & 0xff04) == 0) {
+ 16| local_88[0] = DAT_0041d150 & 0xff04 | param_2;
+ 17| }
+ ...
+ */
+
+ @Test
+ public void testLocalNameTransferAction() throws RuntimeException {
+ final String actionName = ApplyLocalNameFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel uncorrelatedPanel =
+ preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
+ DockingActionIf localNameTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(localNameTransferAction);
+
+ line = 15;
+ col = 0;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated local variable token 'demangler'
+ // Ensure the local variable name transfer action is not active
+ assertEquals("demangler", currentToken.getText());
+ assertNotEnabled(localNameTransferAction, getProviderContext());
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
+ // Recreated provider, need to get new handle on action
+ localNameTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(localNameTransferAction);
+
+ line = 15;
+ col = 11;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated constant token '0'.
+ // Ensure the local variable name transfer action is not active
+ assertEquals("0", currentToken.getText());
+ assertNotEnabled(localNameTransferAction, getProviderContext());
+
+ line = 17;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated global variable token 'DAT_004252e0'
+ // Ensure the local variable name transfer action is not active
+ assertEquals("DAT_004252e0", currentToken.getText());
+ assertNotEnabled(localNameTransferAction, getProviderContext());
+
+ line = 15;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_28'
+ // Ensure the local variable name transfer action is active and working
+ assertEquals("local_28", currentToken.getText());
+ assertEnabled(localNameTransferAction, getProviderContext());
+
+ performAction(localNameTransferAction);
+ waitForDecompile(correlatedPanel);
+ currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
+ assertEquals("demangler", currentToken.getText());
+ }
+
+ @Test
+ public void testGlobalNameTransferAction() throws RuntimeException {
+ final String actionName = ApplyGlobalNameFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel uncorrelatedPanel =
+ preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
+ DockingActionIf globalNameTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(globalNameTransferAction);
+
+ line = 17;
+ col = 0;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated global variable token 'program_name'
+ // Ensure the global variable name transfer action is not active
+ assertEquals("program_name", currentToken.getText());
+ assertNotEnabled(globalNameTransferAction, getProviderContext());
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
+ // Recreated provider, need to get new handle on action
+ globalNameTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(globalNameTransferAction);
+
+ line = 15;
+ col = 11;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated constant token '0'.
+ // Ensure the global variable name transfer action is not active
+ assertEquals("0", currentToken.getText());
+ assertNotEnabled(globalNameTransferAction, getProviderContext());
+
+ line = 15;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_28'
+ // Ensure the global variable name transfer action is not active
+ assertEquals("local_28", currentToken.getText());
+ assertNotEnabled(globalNameTransferAction, getProviderContext());
+
+ line = 17;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated global variable token 'DAT_004252e0'
+ // Ensure the global variable name transfer action is active and working
+ assertEquals("DAT_004252e0", currentToken.getText());
+ assertEnabled(globalNameTransferAction, getProviderContext());
+ performAction(globalNameTransferAction);
+ waitForDecompile(correlatedPanel);
+ currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
+ assertEquals("program_name", currentToken.getText());
+ }
+
+ @Test
+ public void testVariableTypeTransferAction() throws RuntimeException {
+ final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel uncorrelatedPanel =
+ preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
+ DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(typeTransferAction);
+
+ line = 15;
+ col = 0;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated local variable token 'demangler'
+ // Ensure the variable type transfer action is not active
+ assertEquals("demangler", currentToken.getText());
+ assertNotEnabled(typeTransferAction, getProviderContext());
+
+ line = 17;
+ col = 0;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated global variable token 'program_name'
+ // Ensure the variable type transfer action is not active
+ assertEquals("program_name", currentToken.getText());
+ assertNotEnabled(typeTransferAction, getProviderContext());
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
+ // Recreated provider, need to get new handle on action
+ typeTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(typeTransferAction);
+
+ line = 15;
+ col = 11;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated constant token '0'.
+ // Ensure the variable type transfer action is not active
+ assertEquals("0", currentToken.getText());
+ assertNotEnabled(typeTransferAction, getProviderContext());
+
+ line = 15;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_28'
+ // Ensure the variable type transfer action is active and works on local variable
+ assertEquals("local_28", currentToken.getText());
+ assertEnabled(typeTransferAction, getProviderContext());
+ performAction(typeTransferAction);
+ waitForDecompile(correlatedPanel);
+ // Check that 'local_28 = 0;' has become 'local_28 = (char *)0x0;
+ col = 12;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("char", currentToken.getText());
+ col = 17;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("*", currentToken.getText());
+
+ line = 17;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated global variable token 'DAT_004252e0'
+ // Ensure the global variable name transfer action is active and working
+ assertEquals("DAT_004252e0", currentToken.getText());
+ assertEnabled(typeTransferAction, getProviderContext());
+ performAction(typeTransferAction);
+ waitForDecompile(correlatedPanel);
+ // Check that 'DAT_004252e0 = *param_2;' has become 'PTR_004252e0 = (char *)*param_2;'
+ currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
+ assertEquals("PTR_004252e0", currentToken.getText());
+ col = 16;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("char", currentToken.getText());
+ col = 21;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("*", currentToken.getText());
+ }
+
+ /**
+ * The callee and struct type tests use the decomp of the following functions. We care about
+ * the work_stuff struct and the call to 'internal_cplus_demangle' / 'FUN_0040c987', which has
+ * debug signature: char * internal_cplus_demangle(work_stuff *work, char *mangled)
+ * stripped signature: undefined8 FUN_0040c987(uint *param_1,char *param_2):
+
+ Decomp of 'cplus_demangle' in 'demangler_gnu_v2_24_fulldebug':
+ 1|
+ 2| char * cplus_demangle(char *mangled,int options)
+ 3|
+ 4| {
+ 5| char *pcVar1;
+ 6| int options_local;
+ 7| char *mangled_local;
+ 8| work_stuff work [1];
+ 9| char *ret;
+ 10|
+ 11| if (current_demangling_style == no_demangling) {
+ 12| pcVar1 = xstrdup(mangled);
+ 13| }
+ 14| else {
+ 15| memset(work,0,0x70);
+ 16| work[0].options = options;
+ 17| if ((options & 0xff04U) == 0) {
+ 18| work[0].options =
+ 19| current_demangling_style &
+ 20| (gnat_demangling|gnu_v3_demangling|edg_demangling|hp_demangling|arm_demangling|
+ 21| lucid_demangling|gnu_demangling|auto_demangling|java_demangling) | options;
+ 22| }
+ 23| if (((((work[0].options & 0x4000U) == 0) && ((work[0].options & 0x100U) == 0)) ||
+ 24| ((pcVar1 = cplus_demangle_v3(mangled,work[0].options), pcVar1 == (char *)0x0 &&
+ 25| ((work[0].options & 0x4000U) == 0)))) &&
+ 26| (((work[0].options & 4U) == 0 || (pcVar1 = java_demangle_v3(mangled), pcVar1 == (char *)0x0))
+ 27| )) {
+ 28| if ((work[0].options & 0x8000U) == 0) {
+ 29| pcVar1 = internal_cplus_demangle(work,mangled);
+ 30| squangle_mop_up(work);
+ 31| }
+ 32| else {
+ 33| pcVar1 = ada_demangle(mangled,options);
+ 34| }
+ 35| }
+ 36| }
+ 37| return pcVar1;
+ 38| }
+
+
+ Decomp of 'FUN_0040c01b' (cplus_demangle) in 'demangler_gnu_v2_24_stripped':
+ 1|
+ 2| long FUN_0040c01b(undefined8 param_1,uint param_2)
+ 3|
+ 4| {
+ 5| long lVar1;
+ 6| uint local_88 [30];
+ 7| long local_10;
+ 8|
+ 9| if (DAT_0041d150 == 0xffffffff) {
+ 10| local_10 = FUN_0040b419(param_1);
+ 11| }
+ 12| else {
+ 13| memset(local_88,0,0x70);
+ 14| local_88[0] = param_2;
+ 15| if ((param_2 & 0xff04) == 0) {
+ 16| local_88[0] = DAT_0041d150 & 0xff04 | param_2;
+ 17| }
+ 18| if (((((local_88[0] & 0x4000) == 0) && (lVar1 = local_10, (local_88[0] & 0x100) == 0)) ||
+ 19| ((local_10 = FUN_0040a8cc(param_1,local_88[0]), local_10 == 0 &&
+ 20| (lVar1 = 0, (local_88[0] & 0x4000) == 0)))) &&
+ 21| ((local_10 = lVar1, (local_88[0] & 4) == 0 ||
+ 22| (local_10 = FUN_0040a922(param_1), local_10 == 0)))) {
+ 23| if ((local_88[0] & 0x8000) == 0) {
+ 24| local_10 = FUN_0040c987(local_88,param_1);
+ 25| FUN_0040cb6c(local_88);
+ 26| }
+ 27| else {
+ 28| local_10 = FUN_0040c154(param_1,param_2);
+ 29| }
+ 30| }
+ 31| }
+ 32| return local_10;
+ 33| }
+ */
+
+ @Test
+ public void testFullStructTypeTransferAction() throws RuntimeException {
+ final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
+ DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(typeTransferAction);
+
+ line = 14;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_88'.
+ // Ensure the variable type transfer action is active, and
+ assertEquals("local_88", currentToken.getText());
+ assertEnabled(typeTransferAction, getProviderContext());
+ performAction(typeTransferAction);
+ waitForDecompile(correlatedPanel);
+
+ // Sanity check: options field exists
+ line = 13;
+ col = 12;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("options", currentToken.getText());
+
+ // Check that the work_stuff struct is fully in the data type manager
+ ProgramBasedDataTypeManager debugDtm = progDemangler24Debug.getDataTypeManager();
+ Structure debugStructure =
+ (Structure) debugDtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
+ assertNotNull(debugStructure);
+
+ ProgramBasedDataTypeManager strippedDtm = progDemangler24Stripped.getDataTypeManager();
+ Structure strippedStructure = (Structure) strippedDtm
+ .getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
+ assertNotNull(strippedStructure);
+
+ assertEquals(debugStructure.getNumComponents(), strippedStructure.getNumComponents());
+ }
+
+ @Test
+ public void testSkeletonStructTypeTransferAction() throws RuntimeException {
+ final String actionName = ApplyEmptyVariableTypeFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
+ DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(typeTransferAction);
+
+ line = 14;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+
+ // Cursor is now on correlated local variable token 'local_88'.
+ // Ensure the variable type transfer action is active, and
+ assertEquals("local_88", currentToken.getText());
+ assertEnabled(typeTransferAction, getProviderContext());
+ performAction(typeTransferAction);
+ waitForDecompile(correlatedPanel);
+
+ // Sanity check: variable declaration is retyped
+ line = 6;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("work_stuff", currentToken.getText());
+
+ // Check that an empty struct is in the data type manager
+ ProgramBasedDataTypeManager dtm = progDemangler24Stripped.getDataTypeManager();
+ Structure structure =
+ (Structure) dtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
+
+ assertNotNull(structure);
+ assertTrue(structure.isNotYetDefined());
+ assertTrue(structure.isZeroLength());
+ assertEquals(0, structure.getNumComponents());
+ }
+
+ @Test
+ public void testCalleeNameTransferAction() throws RuntimeException {
+ final String actionName = ApplyCalleeFunctionNameFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel uncorrelatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
+ DockingActionIf calleeNameTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(calleeNameTransferAction);
+
+ line = 12;
+ col = 9;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated callee token 'xstrdup'
+ // Ensure the callee name transfer action is not active
+ assertEquals("xstrdup", currentToken.getText());
+ assertNotEnabled(calleeNameTransferAction, getProviderContext());
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
+ // Recreated provider, need to get new handle on action
+ calleeNameTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(calleeNameTransferAction);
+
+ line = 9;
+ col = 4;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated global variable token 'DAT_0041d150'
+ // Ensure the callee name transfer action is not active
+ assertEquals("DAT_0041d150", currentToken.getText());
+ assertNotEnabled(calleeNameTransferAction, getProviderContext());
+
+ line = 10;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_10'
+ // Ensure the callee name transfer action is not active
+ assertEquals("local_10", currentToken.getText());
+ assertNotEnabled(calleeNameTransferAction, getProviderContext());
+
+ line = 24;
+ col = 11;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated callee token 'FUN_0040c987'
+ // Ensure the callee name transfer action is active and working
+ assertEquals("FUN_0040c987", currentToken.getText());
+ assertEnabled(calleeNameTransferAction, getProviderContext());
+ performAction(calleeNameTransferAction);
+ waitForDecompile(correlatedPanel);
+
+ // Check updated function name
+ currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
+ assertEquals("internal_cplus_demangle", currentToken.getText());
+ }
+
+ @Test
+ public void testCalleeFullTypeTransferAction() throws RuntimeException {
+ final String actionName =
+ ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel uncorrelatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
+ DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(calleeFullSignatureTransferAction);
+
+ line = 12;
+ col = 9;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated callee token 'xstrdup'
+ // Ensure the callee name transfer action is not active
+ assertEquals("xstrdup", currentToken.getText());
+ assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
+ // Recreated provider, need to get new handle on action
+ calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(calleeFullSignatureTransferAction);
+
+ line = 9;
+ col = 4;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated global variable token 'DAT_0041d150'
+ // Ensure the callee name transfer action is not active
+ assertEquals("DAT_0041d150", currentToken.getText());
+ assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
+
+ line = 10;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_10'
+ // Ensure the callee name transfer action is not active
+ assertEquals("local_10", currentToken.getText());
+ assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
+
+ line = 24;
+ col = 11;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated callee token 'FUN_0040c987'
+ // Ensure the callee name transfer action is active and working
+ assertEquals("FUN_0040c987", currentToken.getText());
+ assertEnabled(calleeFullSignatureTransferAction, getProviderContext());
+ performAction(calleeFullSignatureTransferAction);
+ waitForDecompile(correlatedPanel);
+
+ // Check updated function name
+ line = 25;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ assertEquals("internal_cplus_demangle", currentToken.getText());
+
+ // Check updated function signature
+ FunctionSignature debugSig = getFunctionFromOffset(progDemangler24Debug,
+ DEMANGLER_24_DEBUG_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
+ FunctionSignature strippedSig = getFunctionFromOffset(progDemangler24Stripped,
+ DEMANGLER_24_STRIPPED_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
+
+ assertEquals(debugSig.getReturnType().getDisplayName(),
+ strippedSig.getReturnType().getDisplayName());
+ assertEquals(debugSig.getArguments().length, strippedSig.getArguments().length);
+
+ for (int i = 0; i < debugSig.getArguments().length; i++) {
+ ParameterDefinition p = debugSig.getArguments()[i];
+ ParameterDefinition p2 = strippedSig.getArguments()[i];
+ assertEquals(p.getDataType().getDisplayName(), p2.getDataType().getDisplayName());
+ }
+
+ // Check full datatype for work_stuff
+ ProgramBasedDataTypeManager debugDtm = progDemangler24Debug.getDataTypeManager();
+ Structure debugStructure =
+ (Structure) debugDtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
+ assertNotNull(debugStructure);
+
+ ProgramBasedDataTypeManager strippedDtm = progDemangler24Stripped.getDataTypeManager();
+ Structure strippedStructure = (Structure) strippedDtm
+ .getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
+ assertNotNull(strippedStructure);
+
+ assertEquals(debugStructure.getNumComponents(), strippedStructure.getNumComponents());
+ }
+
+ @Test
+ public void testCalleeSkeletonTypeTransferAction() throws RuntimeException {
+ final String actionName = ApplyCalleeEmptySignatureFromMatchedTokensAction.ACTION_NAME;
+ int line;
+ int col;
+ ClangToken currentToken;
+
+ DecompilerCodeComparisonPanel uncorrelatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
+ DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(calleeFullSignatureTransferAction);
+
+ line = 12;
+ col = 9;
+ currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
+ // Cursor is now on uncorrelated callee token 'xstrdup'
+ // Ensure the callee name transfer action is not active
+ assertEquals("xstrdup", currentToken.getText());
+ assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
+
+ DecompilerCodeComparisonPanel correlatedPanel =
+ preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
+ // Recreated provider, need to get new handle on action
+ calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
+ assertNotNull(calleeFullSignatureTransferAction);
+
+ line = 9;
+ col = 4;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated global variable token 'DAT_0041d150'
+ // Ensure the callee name transfer action is not active
+ assertEquals("DAT_0041d150", currentToken.getText());
+ assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
+
+ line = 10;
+ col = 0;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated local variable token 'local_10'
+ // Ensure the callee name transfer action is not active
+ assertEquals("local_10", currentToken.getText());
+ assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
+
+ line = 24;
+ col = 11;
+ currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
+ // Cursor is now on correlated callee token 'FUN_0040c987'
+ // Ensure the callee name transfer action is active and working
+ assertEquals("FUN_0040c987", currentToken.getText());
+ assertEnabled(calleeFullSignatureTransferAction, getProviderContext());
+ performAction(calleeFullSignatureTransferAction);
+ waitForDecompile(correlatedPanel);
+
+ // Check updated function name
+ currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
+ assertEquals("internal_cplus_demangle", currentToken.getText());
+
+ // Check updated function signature
+ FunctionSignature debugSig = getFunctionFromOffset(progDemangler24Debug,
+ DEMANGLER_24_DEBUG_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
+ FunctionSignature strippedSig = getFunctionFromOffset(progDemangler24Stripped,
+ DEMANGLER_24_STRIPPED_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
+
+ assertEquals(debugSig.getReturnType().getDisplayName(),
+ strippedSig.getReturnType().getDisplayName());
+ assertEquals(debugSig.getArguments().length, strippedSig.getArguments().length);
+
+ for (int i = 0; i < debugSig.getArguments().length; i++) {
+ ParameterDefinition p = debugSig.getArguments()[i];
+ ParameterDefinition p2 = strippedSig.getArguments()[i];
+ assertEquals(p.getDataType().getDisplayName(), p2.getDataType().getDisplayName());
+ }
+
+ // Check that an empty struct is in the data type manager
+ ProgramBasedDataTypeManager dtm = progDemangler24Stripped.getDataTypeManager();
+ Structure structure =
+ (Structure) dtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
+
+ assertNotNull(structure);
+ assertTrue(structure.isNotYetDefined());
+ assertTrue(structure.isZeroLength());
+ assertEquals(0, structure.getNumComponents());
+ }
+
+ // Setup and focus to a decompiler comparison between the two selected functions. Wait for
+ // the decompilation to complete so that subsequent calls to navigation, etc. work correctly
+ private DecompilerCodeComparisonPanel preparePanel(Function leftFunc, Function rightFunc) {
+ if (provider != null) {
+ // Always want to clear out existing comparison
+ closeProvider(provider);
+ }
+
+ provider = compareFunctions(Set.of(leftFunc, rightFunc));
+
+ DecompilerCodeComparisonPanel decompPanel = findDecompilerPanel(provider);
+ waitForDecompile(decompPanel);
+ decompPanel.setSynchronizedScrolling(true);
+ setActivePanel(provider, decompPanel);
+
+ assertEquals(decompPanel.getFunction(Side.LEFT), leftFunc);
+ assertEquals(decompPanel.getFunction(Side.RIGHT), rightFunc);
+
+ return decompPanel;
+ }
+
+ private Function getFunctionFromOffset(Program prog, long offset) {
+ return prog.getFunctionManager()
+ .getFunctionAt(
+ prog.getAddressFactory().getDefaultAddressSpace().getAddress(offset));
+ }
+
+ private void assertEnabled(DockingActionIf action, ActionContext context) {
+ assertTrue(runSwing(() -> action.isEnabledForContext(context)));
+ }
+
+ private void assertNotEnabled(DockingActionIf action, ActionContext context) {
+ assertFalse(runSwing(() -> action.isEnabledForContext(context)));
+ }
+
+ // Test programs are always opened read-only, so set context override
+ private ActionContext getProviderContext() {
+ ActionContext context = provider.getActionContext(null);
+ if (context instanceof DualDecompilerActionContext dualDecompContext) {
+ dualDecompContext.setOverrideReadOnly(true);
+ }
+
+ return context;
+ }
+
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/test/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java
similarity index 95%
rename from Ghidra/Features/CodeCompare/src/main/test/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java
rename to Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java
index 691fec0c9f..5b163a32da 100644
--- a/Ghidra/Features/CodeCompare/src/main/test/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java
+++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.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.
@@ -35,8 +35,8 @@ import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel;
+import ghidra.features.base.codecompare.panel.CodeComparisonPanel;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
-import ghidra.features.codecompare.plugin.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.ByteDataType;
@@ -130,12 +130,15 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
}
@Test
- public void testNextPreviousActionSwitchPanelFocus() {
+ public void testNextPreviousActionSwitchPanelFocus() throws Exception {
Set functions = Set.of(foo, bar);
provider = compareFunctions(functions);
DockingActionIf nextAction = getAction(plugin, "Compare Next Function");
DockingActionIf previousAction = getAction(plugin, "Compare Previous Function");
+ // since we are clicking the listing panel, bring that to the front first
+ setActivePanel(provider, provider.getComponent().getDualListingPanel());
+
// left panel has focus, so nextAction should be enabled and previous should be disabled
ActionContext context = provider.getActionContext(null);
assertEnabled(nextAction, context);
@@ -288,6 +291,11 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
assertFalse(runSwing(() -> action.isEnabledForContext(context)));
}
+ private void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) {
+ runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName()));
+ waitForSwing();
+ }
+
private FunctionComparisonProvider compareFunctions(Set functions) {
runSwing(() -> plugin.createComparison(functions));
waitForSwing();