GP-3696 - cleaning up function compare windows.

This commit is contained in:
ghidragon 2024-05-23 11:26:17 -04:00
parent 770f5447e1
commit 5ea8e97805
77 changed files with 4065 additions and 5654 deletions

View file

@ -1,262 +0,0 @@
/* ###
* 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.codecompare;
import java.util.List;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.ToolBarData;
import generic.theme.GIcon;
import ghidra.app.decompiler.component.DecompileData;
import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel;
import ghidra.app.decompiler.component.DualDecompileResultsListener;
import ghidra.app.decompiler.component.DualDecompilerActionContext;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskBuilder;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskListener;
import resources.Icons;
import resources.MultiIcon;
/**
* This is a CodeComparisonPanel that gets discovered by other providers that display dual
* comparison views.<br>
* Note: Therefore there may not be any other classes that refer directly to it.
*/
public class DecompilerDiffCodeComparisonPanel
extends DecompilerCodeComparisonPanel<CodeDiffFieldPanelCoordinator>
implements DualDecompileResultsListener, OptionsChangeListener {
public static final String CODE_DIFF_VIEW = "Decompiler Diff View";
private static final String HELP_TOPIC = "FunctionComparison";
private DecompileDataDiff decompileDataDiff;
private DiffClangHighlightController leftHighlightController;
private DiffClangHighlightController rightHighlightController;
private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator;
private MyToggleExactConstantMatching toggleExactConstantMatchingAction;
private boolean isMatchingConstantsExactly = true;
private boolean toggleFlagWhenLastVisible = isMatchingConstantsExactly;
private CompareFuncsFromMatchedTokensAction compareFuncsAction;
private DecompilerCodeComparisonOptions comparisonOptions;
/**
* Constructor
* @param owner owner
* @param tool tool
*/
public DecompilerDiffCodeComparisonPanel(String owner, PluginTool tool) {
super(owner, tool);
comparisonOptions = new DecompilerCodeComparisonOptions();
initializeOptions();
leftHighlightController = new DiffClangHighlightController(comparisonOptions);
rightHighlightController = new DiffClangHighlightController(comparisonOptions);
setHighlightControllers(leftHighlightController, rightHighlightController);
// Make the left highlight listen to the right.
leftHighlightController.addListener(rightHighlightController);
// Make the right highlight listen to the left.
rightHighlightController.addListener(leftHighlightController);
addDualDecompileResultsListener(this);
decompilerFieldPanelCoordinator = new CodeDiffFieldPanelCoordinator(this);
setFieldPanelCoordinator(decompilerFieldPanelCoordinator);
}
private void initializeOptions() {
ToolOptions options =
tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
options.addOptionsChangeListener(this);
comparisonOptions.registerOptions(options);
comparisonOptions.loadOptions(options);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
comparisonOptions.loadOptions(options);
repaint();
}
@Override
public void setVisible(boolean aFlag) {
if (aFlag == isVisible()) {
return;
}
if (aFlag) {
// Becoming visible.
if (toggleFlagWhenLastVisible != isMatchingConstantsExactly) {
if (decompileDataDiff != null) {
determineDecompilerDifferences();
}
}
}
else {
// No longer visible.
toggleFlagWhenLastVisible = isMatchingConstantsExactly;
}
super.setVisible(aFlag);
updateActionEnablement();
}
@Override
public String getTitle() {
return CODE_DIFF_VIEW;
}
@Override
public void decompileResultsSet(DecompileData leftDecompileResults,
DecompileData rightDecompileResults) {
if ((leftDecompileResults == null) || (rightDecompileResults == null) ||
(leftDecompileResults.getFunction() == null) ||
(rightDecompileResults.getFunction() == null)) {
return;
}
decompileDataDiff = new DecompileDataDiff(leftDecompileResults, rightDecompileResults);
determineDecompilerDifferences();
}
List<TokenBin> getHighBins() {
return decompilerFieldPanelCoordinator.getHighBins();
}
private void determineDecompilerDifferences() {
if (decompileDataDiff == null) {
return;
}
DetermineDecompilerDifferencesTask task =
new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly,
leftHighlightController, rightHighlightController, decompilerFieldPanelCoordinator);
task.addTaskListener(new TaskListener() {
@Override
public void taskCompleted(Task completedTask) {
// Does this need anything here?
}
@Override
public void taskCancelled(Task cancelledTask) {
// Does this need anything here?
}
});
new TaskLauncher(task, getComponent());
}
@Override
protected void createActions() {
super.createActions();
toggleExactConstantMatchingAction = new MyToggleExactConstantMatching(getClass().getName());
compareFuncsAction = new CompareFuncsFromMatchedTokensAction(this, tool);
}
@Override
public DockingAction[] getActions() {
DockingAction[] parentActions = super.getActions();
DockingAction[] myActions =
new DockingAction[] { toggleExactConstantMatchingAction, compareFuncsAction };
DockingAction[] allActions = new DockingAction[parentActions.length + myActions.length];
System.arraycopy(parentActions, 0, allActions, 0, parentActions.length);
System.arraycopy(myActions, 0, allActions, parentActions.length, myActions.length);
return allActions;
}
@Override
public void updateActionEnablement() {
// Need to enable/disable toolbar button.
toggleExactConstantMatchingAction.setEnabled(isVisible());
}
public class MyToggleExactConstantMatching extends ToggleDockingAction {
private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c");
private final Icon NO_EXACT_CONSTANT_MATCHING_ICON =
new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON);
/**
* Creates an action for toggling exact constant matching in the code diff's
* dual decompiler.
* @param owner the owner of this action (typically the provider).
*/
public MyToggleExactConstantMatching(String owner) {
super("Toggle Exact Constant Matching", owner);
setHelpLocation(new HelpLocation(HELP_TOPIC, "Toggle Exact Constant Matching"));
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" +
"be exactly the same value to be a match\n" + "in the " + CODE_DIFF_VIEW + "."));
setSelected(false);
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (context instanceof DualDecompilerActionContext) {
return true;
}
return false;
}
@Override
public void actionPerformed(ActionContext context) {
isMatchingConstantsExactly = !isSelected();
if (DecompilerDiffCodeComparisonPanel.this.isVisible()) {
DecompilerDiffCodeComparisonPanel.this.determineDecompilerDifferences();
}
}
@Override
public void setSelected(boolean selected) {
getToolBarData().setIcon(
selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON);
super.setSelected(selected);
}
}
@Override
protected CodeDiffFieldPanelCoordinator createFieldPanelCoordinator() {
CodeDiffFieldPanelCoordinator coordinator = new CodeDiffFieldPanelCoordinator(this);
if (decompileDataDiff != null) {
TaskBuilder.withRunnable(monitor -> {
try {
coordinator.replaceDecompileDataDiff(decompileDataDiff,
isMatchingConstantsExactly, monitor);
}
catch (CancelledException e) {
coordinator.clearLineNumberPairing();
}
}).setTitle("Initializing Code Compare").launchNonModal();
}
return coordinator;
}
}

View file

@ -26,6 +26,7 @@ import generic.theme.GColor;
import ghidra.app.decompiler.ClangSyntaxToken;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.*;
import ghidra.codecompare.decompile.DecompilerCodeComparisonOptions;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.util.ColorUtils;
import ghidra.util.SystemUtilities;

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.util.Iterator;
import java.util.List;
@ -22,16 +24,17 @@ import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.DecompilerLocation;
import ghidra.app.decompiler.component.*;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.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 DecompilerDiffCodeComparisonPanel}
*/
public abstract class AbstractMatchedTokensAction extends DockingAction {
protected DecompilerDiffCodeComparisonPanel diffPanel;
protected DecompilerCodeComparisonPanel diffPanel;
protected boolean disableOnReadOnly;
/**
@ -43,7 +46,7 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
* @param disableOnReadOnly if true, action will be disabled for read-only programs
*/
public AbstractMatchedTokensAction(String actionName, String owner,
DecompilerDiffCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
super(actionName, owner);
this.diffPanel = diffPanel;
this.disableOnReadOnly = disableOnReadOnly;
@ -63,26 +66,20 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
if (!(context instanceof DualDecompilerActionContext compareContext)) {
return false;
}
if (!(compareContext
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
return false;
}
DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
if (disableOnReadOnly) {
//get the program corresponding to the panel with focus
Program program = decompPanel.getLeftProgram();
Side focusedSide = decompPanel.getActiveSide();
Program program = decompPanel.getProgram(focusedSide);
if (program == null) {
return false; //panel initializing; don't enable action
}
if (!decompPanel.leftPanelHasFocus()) {
program = decompPanel.getRightProgram();
}
if (!program.canSave()) {
return false; //program is read-only, don't enable action
}
}
@SuppressWarnings("unchecked")
TokenPair currentPair = getCurrentTokenPair(decompPanel);
return enabledForTokens(currentPair);
@ -96,9 +93,9 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
* @return matching tokens (or null if no match)
*/
protected TokenPair getCurrentTokenPair(
DecompilerCodeComparisonPanel<? extends DualDecompilerFieldPanelCoordinator> decompPanel) {
DecompilerCodeComparisonPanel decompPanel) {
DecompilerPanel focusedPanel = decompPanel.getFocusedDecompilerPanel().getDecompilerPanel();
DecompilerPanel focusedPanel = decompPanel.getActiveDisplay().getDecompilerPanel();
if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
return null;
@ -126,7 +123,8 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
while (tokenIter.hasNext()) {
ClangToken currentMatch = tokenIter.next();
if (currentMatch.getClass().equals(focusedToken.getClass())) {
return decompPanel.leftPanelHasFocus() ? new TokenPair(focusedToken, currentMatch)
return decompPanel.getActiveSide() == LEFT
? new TokenPair(focusedToken, currentMatch)
: new TokenPair(currentMatch, focusedToken);
}
}

View file

@ -0,0 +1,200 @@
/* ###
* 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.codecompare.decompile;
import java.math.BigInteger;
import java.util.function.Consumer;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.GhidraOptions;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.component.*;
import ghidra.codecompare.DiffClangHighlightController;
import ghidra.framework.options.ToolOptions;
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.util.ProgramLocation;
/**
* Represents one side of a dual decompiler compare window. It holds the decompiler controller and
* related state information for one side.
*/
public class CDisplay {
private final static String OPTIONS_TITLE = "Decompiler";
private DecompilerController controller;
private DecompileOptions decompileOptions;
private FieldLocation lastCursorPosition;
private DiffClangHighlightController highlightController;
private Program program;
private DecompilerProgramListener programListener;
public CDisplay(DecompilerCodeComparisonOptions comparisonOptions,
DecompileResultsListener decompileListener,
Consumer<ProgramLocation> locationConsumer) {
highlightController = new DiffClangHighlightController(comparisonOptions);
decompileOptions = new DecompileOptions();
DecompilerCallbackHandler handler = new DecompilerCallbackHandlerAdapter() {
@Override
public void locationChanged(ProgramLocation programLocation) {
locationConsumer.accept(programLocation);
}
};
controller = new DecompilerController(handler, decompileOptions, null) {
@Override
public void setDecompileData(DecompileData decompileData) {
super.setDecompileData(decompileData);
decompileListener.setDecompileData(decompileData);
controller.getDecompilerPanel().validate();
}
};
controller.getDecompilerPanel().setHighlightController(highlightController);
programListener = new DecompilerProgramListener(controller, () -> refresh());
}
public DecompilerPanel getDecompilerPanel() {
return controller.getDecompilerPanel();
}
private void updateProgram(PluginTool tool, Function function) {
Program newProgram = function == null ? null : function.getProgram();
if (program == newProgram) {
return;
}
if (program != null) {
program.removeListener(programListener);
}
program = newProgram;
if (program != null) {
program.addListener(programListener);
initializeOptions(tool, function);
}
}
public void showFunction(PluginTool tool, Function function) {
updateProgram(tool, function);
lastCursorPosition = null;
if (function == null) {
clearAndShowMessage("No Function");
return;
}
if (function.isExternal()) {
clearAndShowMessage("\"" + function.getName(true) + "\" is an external function.");
return;
}
Address entry = function.getEntryPoint();
ProgramLocation location = new ProgramLocation(program, entry);
controller.display(program, location, new ViewerPosition(0, 0, 0));
}
public void clearAndShowMessage(String message) {
controller.setDecompileData(new EmptyDecompileData(message));
DecompilerPanel decompilerPanel = getDecompilerPanel();
decompilerPanel.paintImmediately(decompilerPanel.getBounds());
}
public void setMouseNavigationEnabled(boolean enabled) {
controller.setMouseNavigationEnabled(enabled);
}
public void dispose() {
if (program != null) {
program.removeListener(programListener);
program = null;
}
programListener.dispose();
controller.dispose();
}
public DecompilerController getController() {
return controller;
}
public void refresh() {
saveCursorPosition();
DecompileData data = getDecompileData();
if (data != null) {
controller.refreshDisplay(data.getProgram(), data.getLocation(), null);
}
}
public void programClosed(Program closedProgram) {
if (closedProgram == this.program) {
controller.clear();
controller.programClosed(closedProgram);
updateProgram(null, null);
}
}
public boolean isBusy() {
return controller.isDecompiling();
}
public DecompileData getDecompileData() {
DecompileData decompileData = controller.getDecompileData();
if (decompileData instanceof EmptyDecompileData) {
return null;
}
return decompileData;
}
public void initializeOptions(PluginTool tool, Function function) {
if (tool == null) {
return;
}
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
ToolOptions options = tool.getOptions(OPTIONS_TITLE);
Program program = function == null ? null : function.getProgram();
decompileOptions.grabFromToolAndProgram(fieldOptions, options, program);
}
DiffClangHighlightController getHighlightController() {
return highlightController;
}
private void saveCursorPosition() {
lastCursorPosition = getDecompilerPanel().getFieldPanel().getCursorLocation();
}
void restoreCursorPosition() {
if (lastCursorPosition != null) {
BigInteger index = lastCursorPosition.getIndex();
int fieldNum = lastCursorPosition.getFieldNum();
int row = lastCursorPosition.getRow();
int col = lastCursorPosition.getCol();
FieldPanel fieldPanel = getDecompilerPanel().getFieldPanel();
fieldPanel.setCursorPosition(index, fieldNum, row, col);
}
}
}

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.math.BigInteger;
import java.util.ArrayList;
@ -25,7 +27,6 @@ import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.DualDecompilerFieldPanelCoordinator;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.util.ProgramLocation;
@ -52,10 +53,10 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
* Constructor
* @param dualDecompilerPanel decomp comparison panel
*/
public CodeDiffFieldPanelCoordinator(DecompilerDiffCodeComparisonPanel dualDecompilerPanel) {
public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) {
super(dualDecompilerPanel);
this.leftDecompilerPanel = dualDecompilerPanel.getLeftDecompilerPanel();
this.rightDecompilerPanel = dualDecompilerPanel.getRightDecompilerPanel();
this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT);
this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT);
leftToRightLineNumberPairing = new DualHashBidiMap<>();
}

View file

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import docking.ActionContext;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangFuncNameToken;
import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel;
import ghidra.app.decompiler.component.DualDecompilerActionContext;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.PluginTool;
@ -45,7 +45,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
* @param diffPanel diff Panel
* @param tool tool
*/
public CompareFuncsFromMatchedTokensAction(DecompilerDiffCodeComparisonPanel diffPanel,
public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, false);
this.tool = tool;
@ -84,12 +84,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
return;
}
if (!(compareContext
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
return;
}
DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
@SuppressWarnings("unchecked")
TokenPair currentPair = getCurrentTokenPair(decompPanel);
if (currentPair == null || currentPair.leftToken() == null ||
currentPair.rightToken() == null) {
@ -99,8 +95,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getLeftProgram());
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getRightProgram());
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
if (leftFunction == null || rightFunction == null) {
return;
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import java.util.ArrayList;
import java.util.HashSet;

View file

@ -13,18 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import java.awt.Color;
import generic.theme.GColor;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.bean.opteditor.OptionsVetoException;
import utility.function.Callback;
import utility.function.Dummy;
/**
* This class holds the options for the decompiler diff view.
*/
public class DecompilerCodeComparisonOptions {
public class DecompilerCodeComparisonOptions implements OptionsChangeListener {
private static final String MATCHING_TOKEN_HIGHLIGHT_KEY = "Focused Token Match Highlight";
private static final String UNMATCHED_TOKEN_HIGHLIGHT_KEY = "Focused Token Unmatched Highlight";
@ -53,15 +58,18 @@ public class DecompilerCodeComparisonOptions {
private Color unmatchedTokenHighlight;
private Color ineligibleTokenHighlight;
private Color diffHighlight;
private Callback optionsChangedCallback;
public static final String OPTIONS_CATEGORY_NAME = "Decompiler Code Comparison";
public static final String HELP_TOPIC = "FunctionComparison";
/**
* Constructor
*/
public DecompilerCodeComparisonOptions() {
public DecompilerCodeComparisonOptions(PluginTool tool, Callback optionsChangedCallback) {
this.optionsChangedCallback = Dummy.ifNull(optionsChangedCallback);
ToolOptions options =
tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
options.addOptionsChangeListener(this);
registerOptions(options);
loadOptions(options);
}
/**
@ -136,4 +144,11 @@ public class DecompilerCodeComparisonOptions {
return diffHighlight;
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) throws OptionsVetoException {
loadOptions(options);
optionsChangedCallback.call();
}
}

View file

@ -0,0 +1,395 @@
/* ###
* 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.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.*;
import docking.options.OptionsService;
import generic.theme.GIcon;
import ghidra.app.decompiler.component.DecompileData;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.codecompare.DiffClangHighlightController;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskBuilder;
import ghidra.util.task.TaskLauncher;
import resources.Icons;
import resources.MultiIcon;
/**
* Panel that displays two decompilers for comparison
*/
public class DecompilerCodeComparisonPanel
extends CodeComparisonPanel {
public static final String NAME = "Decompile Diff View";
private boolean isStale = true;
private Duo<CDisplay> cDisplays = new Duo<>();
private DecompilerCodeComparisonOptions comparisonOptions;
private CodeDiffFieldPanelCoordinator coordinator;
private DecompileDataDiff decompileDataDiff;
private ToggleExactConstantMatching toggleExactConstantMatchingAction;
private List<DockingAction> actions = new ArrayList<>();
private Duo<Function> functions = new Duo<>();
/**
* Creates a comparison panel with two decompilers
*
* @param owner the owner of this panel
* @param tool the tool displaying this panel
*/
public DecompilerCodeComparisonPanel(String owner, PluginTool tool) {
super(owner, tool);
comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint());
cDisplays = new Duo<>(buildCDisplay(LEFT), buildCDisplay(RIGHT));
cDisplays.get(LEFT).initializeOptions(tool, getFunction(LEFT));
cDisplays.get(RIGHT).initializeOptions(tool, getFunction(RIGHT));
buildPanel();
setSynchronizedScrolling(true);
linkHighlightControllers();
createActions();
}
@Override
public String getName() {
return NAME;
}
@Override
protected void comparisonDataChanged() {
maybeLoadFunction(LEFT, comparisonData.get(LEFT).getFunction());
maybeLoadFunction(RIGHT, comparisonData.get(RIGHT).getFunction());
if (coordinator != null) {
coordinator.leftLocationChanged((ProgramLocation) null);
}
}
public void maybeLoadFunction(Side side, Function function) {
// we keep a local copy of the function so we know if it is already decompiled
if (functions.get(side) == function) {
return;
}
// Clear the scroll info and highlight info to prevent unnecessary highlighting, etc.
loadFunction(side, null);
loadFunction(side, function);
}
@Override
public void dispose() {
setSynchronizedScrolling(false); // disposes any exiting coordinator
cDisplays.each(CDisplay::dispose);
comparisonOptions = null;
}
/**
* Gets the display from the active side.
* @return the active display
*/
public CDisplay getActiveDisplay() {
return cDisplays.get(activeSide);
}
/**
* Gets the left side's C display panel.
* @return the left C display panel
*/
public CDisplay getLeftPanel() {
return cDisplays.get(LEFT);
}
/**
* Gets the right side's C display panel.
* @return the right C display panel
*/
public CDisplay getRightPanel() {
return cDisplays.get(RIGHT);
}
@Override
public List<DockingAction> getActions() {
List<DockingAction> allActions = super.getActions();
allActions.addAll(actions);
return allActions;
}
@Override
public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) {
Component component = event != null ? event.getComponent()
: getActiveDisplay().getDecompilerPanel().getFieldPanel();
DualDecompilerActionContext dualDecompContext =
new DualDecompilerActionContext(provider, this, component);
return dualDecompContext;
}
@Override
public void programClosed(Program program) {
cDisplays.each(c -> c.programClosed(program));
}
@Override
public JComponent getComparisonComponent(Side side) {
return cDisplays.get(side).getDecompilerPanel();
}
private void createActions() {
toggleExactConstantMatchingAction = new ToggleExactConstantMatching(getClass().getName());
actions.add(new DecompilerDiffViewFindAction(owner, tool));
actions.add(new DecompilerCodeComparisonOptionsAction());
actions.add(toggleExactConstantMatchingAction);
actions.add(new CompareFuncsFromMatchedTokensAction(this, tool));
}
private void decompileDataSet(Side side, DecompileData dcompileData) {
cDisplays.get(side).restoreCursorPosition();
updateDiffs();
}
private boolean isMatchingConstantsExactly() {
return !toggleExactConstantMatchingAction.isSelected();
}
private void loadFunction(Side side, Function function) {
if (functions.get(side) != function) {
functions = functions.with(side, function);
cDisplays.get(side).showFunction(tool, function);
}
}
private void locationChanged(Side side, ProgramLocation location) {
if (coordinator != null) {
if (side == LEFT) {
coordinator.leftLocationChanged(location);
}
else {
coordinator.rightLocationChanged(location);
}
}
}
private void updateDiffs() {
DecompileData leftDecompileData = cDisplays.get(LEFT).getDecompileData();
DecompileData rightDecompileData = cDisplays.get(RIGHT).getDecompileData();
if (isValid(leftDecompileData) && isValid(rightDecompileData)) {
decompileDataDiff = new DecompileDataDiff(leftDecompileData, rightDecompileData);
determineDecompilerDifferences();
}
}
private boolean isValid(DecompileData decompileData) {
return decompileData != null && decompileData.isValid();
}
private CDisplay buildCDisplay(Side side) {
return new CDisplay(comparisonOptions,
decompileData -> decompileDataSet(side, decompileData),
l -> locationChanged(side, l));
}
private void linkHighlightControllers() {
DiffClangHighlightController left = cDisplays.get(LEFT).getHighlightController();
DiffClangHighlightController right = cDisplays.get(RIGHT).getHighlightController();
left.addListener(right);
right.addListener(left);
}
@Override
public void setSynchronizedScrolling(boolean synchronize) {
if (coordinator != null) {
coordinator.dispose();
coordinator = null;
}
if (synchronize) {
coordinator = createCoordinator();
doSynchronize();
}
}
private void doSynchronize() {
CDisplay activeDisplay = getActiveDisplay();
ProgramLocation programLocation =
activeDisplay.getDecompilerPanel().getCurrentLocation();
if (activeDisplay == cDisplays.get(LEFT)) {
coordinator.leftLocationChanged(programLocation);
}
else {
coordinator.rightLocationChanged(programLocation);
}
}
private CodeDiffFieldPanelCoordinator createCoordinator() {
CodeDiffFieldPanelCoordinator panelCoordinator = new CodeDiffFieldPanelCoordinator(this);
if (decompileDataDiff != null) {
TaskBuilder.withRunnable(monitor -> {
try {
panelCoordinator.replaceDecompileDataDiff(decompileDataDiff,
isMatchingConstantsExactly(), monitor);
}
catch (CancelledException e) {
panelCoordinator.clearLineNumberPairing();
}
}).setTitle("Initializing Code Compare").launchNonModal();
}
return panelCoordinator;
}
private class DecompilerCodeComparisonOptionsAction extends DockingAction {
DecompilerCodeComparisonOptionsAction() {
super("Decompiler Code Comparison Options", owner);
setDescription("Show the tool options for the Decompiler Code Comparison.");
setPopupMenuData(new MenuData(new String[] { "Properties" }, null, "Z_Properties"));
setHelpLocation(
new HelpLocation("FunctionComparison", "Decompiler_Code_Comparison_Options"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return (context instanceof DualDecompilerActionContext);
}
@Override
public void actionPerformed(ActionContext context) {
OptionsService service = tool.getService(OptionsService.class);
service.showOptionsDialog("FunctionComparison", "Decompiler Code Comparison");
}
}
@Override
public void setVisible(boolean b) {
super.setVisible(b);
if (b && isStale) {
determineDecompilerDifferences();
isStale = false;
}
updateActionEnablement();
}
List<TokenBin> getHighBins() {
return coordinator == null ? null : coordinator.getHighBins();
}
private void determineDecompilerDifferences() {
if (decompileDataDiff == null) {
return;
}
DiffClangHighlightController leftHighlights = cDisplays.get(LEFT).getHighlightController();
DiffClangHighlightController rightHighlights =
cDisplays.get(RIGHT).getHighlightController();
DetermineDecompilerDifferencesTask task =
new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly(),
leftHighlights, rightHighlights, coordinator);
new TaskLauncher(task, this);
}
@Override
public void updateActionEnablement() {
// Need to enable/disable toolbar button.
toggleExactConstantMatchingAction.setEnabled(isVisible());
}
public boolean isBusy() {
return cDisplays.get(LEFT).isBusy() || cDisplays.get(RIGHT).isBusy();
}
public DecompilerPanel getDecompilerPanel(Side side) {
return cDisplays.get(side).getDecompilerPanel();
}
public class ToggleExactConstantMatching extends ToggleDockingAction {
private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c");
private final Icon NO_EXACT_CONSTANT_MATCHING_ICON =
new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON);
/**
* Creates an action for toggling exact constant matching in the code diff's
* dual decompiler.
* @param owner the owner of this action (typically the provider).
*/
public ToggleExactConstantMatching(String owner) {
super("Toggle Exact Constant Matching", owner);
setHelpLocation(new HelpLocation(HELP_TOPIC, "Toggle Exact Constant Matching"));
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" +
"be exactly the same value to be a match\n" + "in the Decomiler Diff View."));
setSelected(false);
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (context instanceof DualDecompilerActionContext) {
return true;
}
return false;
}
@Override
public void actionPerformed(ActionContext context) {
if (isVisible()) {
determineDecompilerDifferences();
}
}
@Override
public void setSelected(boolean selected) {
getToolBarData().setIcon(
selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON);
super.setSelected(selected);
}
}
}

View file

@ -0,0 +1,98 @@
/* ###
* 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.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.KeyEvent;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.DockingUtils;
import docking.action.*;
import docking.widgets.FindDialog;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
public class DecompilerDiffViewFindAction extends DockingAction {
private Duo<FindDialog> findDialogs;
private PluginTool tool;
public DecompilerDiffViewFindAction(String owner, PluginTool tool) {
super("Find", owner, true);
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
setPopupMenuData(new MenuData(new String[] { "Find..." }, "Decompile"));
setKeyBindingData(
new KeyBindingData(KeyEvent.VK_F, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
setEnabled(true);
this.tool = tool;
}
@Override
public boolean isAddToPopup(ActionContext context) {
return (context instanceof DualDecompilerActionContext);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return (context instanceof DualDecompilerActionContext);
}
@Override
public void actionPerformed(ActionContext context) {
DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context;
DecompilerCodeComparisonPanel decompilerCompPanel =
dualContext.getCodeComparisonPanel();
Side focusedSide = decompilerCompPanel.getActiveSide();
DecompilerPanel focusedPanel = decompilerCompPanel.getDecompilerPanel(focusedSide);
FindDialog dialog = findDialogs.get(focusedSide);
if (dialog == null) {
dialog = createFindDialog(focusedPanel, focusedSide);
findDialogs = findDialogs.with(focusedSide, dialog);
}
String text = focusedPanel.getSelectedText();
if (!StringUtils.isBlank(text)) {
dialog.setSearchText(text);
}
tool.showDialog(dialog);
}
private FindDialog createFindDialog(DecompilerPanel decompilerPanel, Side side) {
String title = (side == LEFT ? "Left" : "Right");
title += " Decompiler Find Text";
FindDialog dialog = new FindDialog(title, new DecompilerSearcher(decompilerPanel)) {
@Override
protected void dialogClosed() {
// clear the search results when the dialog is closed
decompilerPanel.setSearchResults(null);
}
};
dialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
return dialog;
}
}

View file

@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import java.util.List;
import java.util.Set;
import ghidra.app.decompiler.ClangToken;
import ghidra.codecompare.DiffClangHighlightController;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;

View file

@ -0,0 +1,52 @@
/* ###
* 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.codecompare.decompile;
import java.awt.Component;
import docking.ComponentProvider;
import ghidra.app.context.RestrictedAddressSetContext;
import ghidra.app.util.viewer.util.CodeComparisonActionContext;
/**
* Action context for a dual decompiler panel.
*/
public class DualDecompilerActionContext extends CodeComparisonActionContext
implements RestrictedAddressSetContext {
private DecompilerCodeComparisonPanel decompilerComparisonPanel = null;
/**
* Creates an action context for a dual decompiler panel.
* @param provider the provider for this context
* @param panel the DecompilerComparisonPanel
* @param source the source of the action
*/
public DualDecompilerActionContext(ComponentProvider provider,
DecompilerCodeComparisonPanel panel, Component source) {
super(provider, panel, source);
this.decompilerComparisonPanel = panel;
}
/**
* Returns the {@link DecompilerCodeComparisonPanel} that generated this context
* @return the decompiler comparison panel that generated this context
*/
@Override
public DecompilerCodeComparisonPanel getCodeComparisonPanel() {
return decompilerComparisonPanel;
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator;
import ghidra.program.util.ProgramLocation;
abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator {
public DualDecompilerFieldPanelCoordinator(
DecompilerCodeComparisonPanel dualDecompilerPanel) {
super(new FieldPanel[] { dualDecompilerPanel.getDecompilerPanel(LEFT).getFieldPanel(),
dualDecompilerPanel.getDecompilerPanel(RIGHT).getFieldPanel() });
}
abstract public void leftLocationChanged(ProgramLocation leftLocation);
abstract public void rightLocationChanged(ProgramLocation rightLocation);
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.codecompare;
package ghidra.codecompare.decompile;
import ghidra.app.decompiler.ClangToken;