custom highlighting for decompiler

This commit is contained in:
00rsiere 2019-07-13 01:55:46 +03:00 committed by dragonmacher
parent 6bac1a8712
commit 0af5a6b4fd
10 changed files with 580 additions and 2 deletions

View file

@ -158,6 +158,11 @@ public class DecompileOptions { //
private final static CommentStyleEnum COMMENTSTYLE_OPTIONDEFAULT = CommentStyleEnum.CStyle;
private CommentStyleEnum commentStyle;
private final static String HIGHLIGHT_OPTIONSTRING = "Display.Custom Highlights";
private final static String HIGHLIGHT_OPTIONDESCRIPTION = "If set, custom highlights is enabled";
private final static boolean HIGHLIGHT_OPTIONDEFAULT = false;
private boolean highlightInclude;
private final static String COMMENTPRE_OPTIONSTRING = "Display.Display PRE comments";
private final static String COMMENTPRE_OPTIONDESCRIPTION =
"If set, disassembly pre-instruction (PRE) comments are displayed " +
@ -366,6 +371,7 @@ public class DecompileOptions { //
indentwidth = opt.getInt(INDENTWIDTH_OPTIONSTRING, INDENTWIDTH_OPTIONDEFAULT);
commentindent = opt.getInt(COMMENTINDENT_OPTIONSTRING, COMMENTINDENT_OPTIONDEFAULT);
commentStyle = opt.getEnum(COMMENTSTYLE_OPTIONSTRING, COMMENTSTYLE_OPTIONDEFAULT);
highlightInclude = opt.getBoolean(HIGHLIGHT_OPTIONSTRING, HIGHLIGHT_OPTIONDEFAULT);
commentEOLInclude = opt.getBoolean(COMMENTEOL_OPTIONSTRING, COMMENTEOL_OPTIONDEFAULT);
commentPREInclude = opt.getBoolean(COMMENTPRE_OPTIONSTRING, COMMENTPRE_OPTIONDEFAULT);
commentPOSTInclude = opt.getBoolean(COMMENTPOST_OPTIONSTRING, COMMENTPOST_OPTIONDEFAULT);
@ -487,6 +493,8 @@ public class DecompileOptions { //
COMMENTSTYLE_OPTIONDESCRIPTION);
opt.registerOption(COMMENTEOL_OPTIONSTRING, COMMENTEOL_OPTIONDEFAULT, help,
COMMENTEOL_OPTIONDESCRIPTION);
opt.registerOption(HIGHLIGHT_OPTIONSTRING, HIGHLIGHT_OPTIONDEFAULT, help,
HIGHLIGHT_OPTIONDESCRIPTION);
opt.registerOption(COMMENTPRE_OPTIONSTRING, COMMENTPRE_OPTIONDEFAULT, help,
COMMENTPRE_OPTIONDESCRIPTION);
opt.registerOption(COMMENTPOST_OPTIONSTRING, COMMENTPOST_OPTIONDEFAULT, help,
@ -681,6 +689,10 @@ public class DecompileOptions { //
return middleMouseHighlightButton;
}
public boolean isHighlightIncluded() {
return highlightInclude;
}
public boolean isPRECommentIncluded() {
return commentPREInclude;
}

View file

@ -72,6 +72,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private Color currentSearchHighlightColor;
private Color currentHighlightColor;
private SearchLocation currentSearchLocation;
private HighlightTokenObservableList<HighlightToken> highlightedTokens = new HighlightTokenObservableList<HighlightToken>(this);
private DecompileData decompileData = new EmptyDecompileData("No Function");
private final DecompilerClipboardProvider clipboard;
@ -116,6 +117,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
setDecompileData(new EmptyDecompileData("No Function"));
}
public HighlightTokenObservableList<HighlightToken> getHighlightedTokens() {
return highlightedTokens;
}
public List<ClangLine> getLines() {
return layoutMgr.getLines();
}
@ -128,6 +133,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return fieldPanel;
}
/**
* This function replace the highlighting tokens with the new list
* @param newTokens the new data
*/
public void setHighlightedTokens(HighlightTokenObservableList<HighlightToken> newTokens) {
highlightedTokens = newTokens;
}
@Override
public void setBackground(Color bg) {
originalBackgroundColor = bg;
@ -635,6 +648,43 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
}
/**
* This function is used to add highlighting to a token.
* The function behaves differently from highlightVariable in that
* it does not erase existing highlights
*
* @param token the token to search for
* @param highlightColor the color to apply
*/
public void addTokenHighlight(ClangToken token, Color highlightColor) {
if (highlightController != null) {
List<ClangToken> tokenList = new ArrayList<>();
findTokensByName(tokenList, layoutMgr.getRoot(), token.getText());
highlightController.addTokensToHighlights(tokenList, highlightColor);
repaint();
}
}
/**
* Add highlighting to all the tokens in highlightedTokens,
* the highlighting accounting structure
*/
public void repaintHighlightTokens(boolean clearHighlights) {
if (clearHighlights) {
clearHighlights();
}
highlightedTokens.forEach(ht -> {
ClangToken token = ht.getToken();
Function tokenFunc = ht.getFunction();
Function decompiledFunc = decompileData.getFunction();
if ((token != null) && (tokenFunc != null) && (decompiledFunc != null) &&
(tokenFunc.getName().equals(decompileData.getFunction().getName()))) {
addTokenHighlight(token, ht.getColor());
}
});
tokenHighlightsChanged();
}
Program getProgram() {
return decompileData.getProgram();
}
@ -656,6 +706,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
if (highlightController != null) {
highlightController.fieldLocationChanged(location, field, trigger);
// We need this because fieldLocationChanged clears the highlighting.
repaintHighlightTokens(false);
}
if (!(field instanceof ClangTextField)) {
@ -900,6 +953,32 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
repaint();
}
/**
* This is function is used to alert the panel that a token was renamed.
* If the token that is being renamed had a special highlight, we must re-apply the highlight
* to the new token.
*
* @param token the token being renamed
* @param name the new name of the token
*/
public void tokenRenamed(ClangToken token, String name) {
HighlightToken htoken = new HighlightToken(token, null, null);
int idx = highlightedTokens.indexOf(htoken);
if (idx >= 0) {
HighlightToken hltok = highlightedTokens.get(idx);
highlightedTokens.remove(hltok);
ClangToken ct = new ClangToken((ClangNode)token, name);
HighlightToken ht = new HighlightToken(ct, hltok.getColor(), null);
highlightedTokens.add(ht);
if (highlightController != null) {
List<ClangToken> tokenList = new ArrayList<>();
findTokensByName(tokenList, layoutMgr.getRoot(), name);
highlightController.addTokensToHighlights(tokenList, getDefaultHighlightColor());
repaint();
}
}
}
//==================================================================================================
// Inner Classes
//==================================================================================================

View file

@ -0,0 +1,86 @@
package ghidra.app.decompiler.component;
import java.awt.Color;
import ghidra.app.decompiler.ClangFunction;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.ClangVariableToken;
import ghidra.program.model.listing.Function;
public class HighlightToken {
private ClangToken token;
private Color color;
private DecompilerPanel panel;
public HighlightToken(ClangToken token, Color color, Function func) {
this.token = token;
this.color = color;
}
/**
* Compares two HighlightTokens.
*
* Two HighlightTokens are equal if their text is equal
* and if they reside in the same function.
*/
@Override
public boolean equals(Object obj) {
ClangToken otherToken = null;
if (obj.getClass() == HighlightToken.class) {
HighlightToken hltoken = (HighlightToken)obj;
otherToken = hltoken.token;
}
if (obj.getClass() == ClangToken.class) {
otherToken = ((ClangToken) obj);
}
if (obj.getClass() == ClangVariableToken.class) {
otherToken = ((ClangToken) obj);
}
if (otherToken == null) {
return false;
}
Function func1 = otherToken.getClangFunction().getHighFunction().getFunction();
Function func2 = this.token.getClangFunction().getHighFunction().getFunction();
return otherToken.getText().equals(this.token.getText()) &&
(func1.equals(func2));
}
public ClangToken getToken() {
return token;
}
public void setToken(ClangToken token) {
this.token = token;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public DecompilerPanel getPanel() {
return panel;
}
public void setPanel(DecompilerPanel panel) {
this.panel = panel;
}
public Function getFunction() {
if (token == null) {
return null;
}
ClangFunction clangFunc = token.getClangFunction();
if (clangFunc == null) {
return null;
}
return clangFunc.getHighFunction().getFunction();
}
}

View file

@ -0,0 +1,49 @@
package ghidra.app.decompiler.component;
import java.util.ArrayList;
public class HighlightTokenObservableList<T> extends ArrayList<T> {
private ArrayList<DecompilerPanel> panels = new ArrayList<DecompilerPanel>();
public HighlightTokenObservableList(DecompilerPanel panel) {
super();
panels.add(panel);
}
public HighlightTokenObservableList() {
super();
}
@Override
public boolean add(T t){
boolean rslt = super.add(t);
notifyListeners();
return rslt;
}
@Override
public void clear() {
super.clear();
notifyListeners();
}
@Override
public boolean remove(Object t){
boolean rslt = super.remove(t);
notifyListeners();
return rslt;
}
public void addListener(DecompilerPanel panel) {
this.panels.add(panel);
}
public void notifyListeners() {
if (panels != null) {
panels.forEach(p -> {
p.repaintHighlightTokens(true);
p.repaint();
});
}
}
}

View file

@ -33,20 +33,28 @@ public class LocationClangHighlightController extends ClangHighlightController {
@Override
public void fieldLocationChanged(FieldLocation location, Field field, EventTrigger trigger) {
clearHighlights();
if (!(field instanceof ClangTextField)) {
clearHighlights();
return;
}
ClangToken tok = ((ClangTextField) field).getToken(location);
if (tok == null) {
clearHighlights();
return;
}
// // clear any highlighted searchResults
// decompilerPanel.setSearchResults(null);
// Token is already highlighted, break out
if (tok.getHighlight() != null) {
return;
}
// Token is not highlighted, clear and re-apply highlighting
clearHighlights();
addHighlight(tok, defaultHighlightColor);
if (tok instanceof ClangSyntaxToken) {
addHighlightParen((ClangSyntaxToken) tok, defaultParenColor);

View file

@ -101,6 +101,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private SelectAllAction selectAllAction;
private SetHighlightAction setHighlightAction;
private RemovePanelHighlightsAction removePanelHighlightsAction;
private RemoveAllHighlightsAction removeAllHighlightsAction;
private ViewerPosition pendingViewerPosition;
private SwingUpdateManager redecompilerUpdater;
@ -311,6 +315,16 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
return;
}
// SetHighlightAction was toggled on/off - notify the handler
if ((setHighlightAction != null) && optionName.equals(setHighlightAction.getName())) {
setHighlightAction.setEnabled((boolean)newValue);
removePanelHighlightsAction.setEnabled((boolean) newValue);
removeAllHighlightsAction.setEnabled((boolean) newValue);
if ((boolean)newValue == false) {
setHighlightAction.reset();
}
}
if (options.getName().equals(OPTIONS_TITLE) ||
options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) {
doRefresh();
@ -616,6 +630,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
Swing.runLater(() -> {
newProvider.doSetProgram(program);
newProvider.controller.setDecompileData(controller.getDecompileData());
// Any change in the HighlightTokens should be delivered to the new panel
getDecompilerPanel().getHighlightedTokens().addListener(newProvider.getDecompilerPanel());
// Transfer the highlighted tokens
newProvider.getDecompilerPanel().setHighlightedTokens(getDecompilerPanel().getHighlightedTokens());
newProvider.setLocation(currentLocation,
controller.getDecompilerPanel().getViewerPosition());
});
@ -768,6 +789,18 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
lockLocalAction = new CommitLocalsAction(tool, controller);
setGroupInfo(lockLocalAction, commitGroup, subGroupPosition++);
String highlightGroup = "4 - Highlight Group";
subGroupPosition = 0; // reset for the next group
setHighlightAction = new SetHighlightAction(owner, controller, decompilerOptions.isHighlightIncluded());
setGroupInfo(setHighlightAction, highlightGroup, subGroupPosition++);
removePanelHighlightsAction = new RemovePanelHighlightsAction(owner, controller, decompilerOptions.isHighlightIncluded());
setGroupInfo(removePanelHighlightsAction, highlightGroup, subGroupPosition++);
removeAllHighlightsAction = new RemoveAllHighlightsAction(owner, controller, decompilerOptions.isHighlightIncluded());
setGroupInfo(removeAllHighlightsAction, highlightGroup, subGroupPosition++);
//
// Comments
//
@ -835,6 +868,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(lockProtoAction);
addLocalAction(lockLocalAction);
addLocalAction(renameVarAction);
addLocalAction(setHighlightAction);
addLocalAction(removePanelHighlightsAction);
addLocalAction(removeAllHighlightsAction);
addLocalAction(retypeVarAction);
addLocalAction(decompilerCreateStructureAction);
tool.addAction(listingCreateStructureAction);

View file

@ -0,0 +1,63 @@
package ghidra.app.plugin.core.decompile.actions;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import ghidra.app.decompiler.component.DecompilerController;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.HighlightToken;
import ghidra.app.decompiler.component.HighlightTokenObservableList;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.listing.Function;
import ghidra.util.HelpLocation;
import ghidra.util.UndefinedFunction;
public class RemoveAllHighlightsAction extends DockingAction {
private DecompilerPanel panel;
private DecompilerController controller;
public RemoveAllHighlightsAction(String owner, DecompilerController controller) {
this(owner, controller, false);
}
public RemoveAllHighlightsAction(String owner, DecompilerController controller, boolean isEnabled) {
super("Remove All Highlights", owner);
this.panel = controller.getDecompilerPanel();
this.controller = controller;
setPopupMenuData(new MenuData(new String[] { "Remove All Highlights" }, "Decompile"));
setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName()));
setEnabled(isEnabled);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DecompilerActionContext)) {
return false;
}
Function function = controller.getFunction();
if (function instanceof UndefinedFunction) {
return false;
}
DecompilerActionContext decompilerActionContext = (DecompilerActionContext) context;
if (decompilerActionContext.isDecompiling()) {
return false;
}
return true;
}
@Override
public void actionPerformed(ActionContext context) {
if (panel != null) {
HighlightTokenObservableList<HighlightToken> highlightTokens = panel.getHighlightedTokens();
highlightTokens.clear();
panel.clearHighlights();
panel.tokenHighlightsChanged();
}
}
}

View file

@ -0,0 +1,111 @@
package ghidra.app.plugin.core.decompile.actions;
import java.util.Iterator;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangFunction;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.DecompilerController;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.HighlightToken;
import ghidra.app.decompiler.component.HighlightTokenObservableList;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.HighFunction;
import ghidra.util.HelpLocation;
import ghidra.util.UndefinedFunction;
public class RemovePanelHighlightsAction extends DockingAction{
private DecompilerPanel panel;
private DecompilerController controller;
public RemovePanelHighlightsAction(String owner, DecompilerController controller) {
this(owner, controller, false);
}
public RemovePanelHighlightsAction(String owner, DecompilerController controller, boolean isEnabled) {
super("Remove Highlights", owner);
this.panel = controller.getDecompilerPanel();
this.controller = controller;
setPopupMenuData(new MenuData(new String[] { "Remove Panel Highlights" }, "Decompile"));
setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName()));
setEnabled(isEnabled);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DecompilerActionContext)) {
return false;
}
Function function = controller.getFunction();
if (function instanceof UndefinedFunction) {
return false;
}
DecompilerActionContext decompilerActionContext = (DecompilerActionContext) context;
if (decompilerActionContext.isDecompiling()) {
return false;
}
return true;
}
@Override
public void actionPerformed(ActionContext context) {
if (panel != null) {
ClangToken tokenAtCursor = panel.getTokenAtCursor();
removeTokensFromFunction(tokenAtCursor.getClangFunction());
panel.clearHighlights();
panel.tokenHighlightsChanged();
}
}
private Function getFunctionFromToken(ClangToken token) {
if (token == null) {
return null;
}
ClangFunction curClangFunction = token.getClangFunction();
if (curClangFunction == null) {
return null;
}
HighFunction curHighFunc = curClangFunction.getHighFunction();
if (curHighFunc == null) {
return null;
}
return curHighFunc.getFunction();
}
private void removeTokensFromFunction(ClangFunction function) {
if (panel != null && function != null) {
HighlightTokenObservableList<HighlightToken> highlightTokens = panel.getHighlightedTokens();
ClangToken curToken = panel.getTokenAtCursor();
Function curFunction = getFunctionFromToken(curToken);
if (curFunction == null) {
return;
}
Iterator<HighlightToken> it = highlightTokens.iterator();
while (it.hasNext()) {
HighlightToken tok = it.next();
Function iterFunction = getFunctionFromToken(tok.getToken());
if (iterFunction.equals(curFunction)) {
it.remove();
}
}
highlightTokens.notifyListeners();
}
}
}

View file

@ -259,6 +259,7 @@ public class RenameVariableAction extends AbstractDecompilerAction {
}
finally {
program.endTransaction(transaction, commit);
controller.getDecompilerPanel().tokenRenamed(tokenAtCursor, nameTask.getNewName());
}
}
}

View file

@ -0,0 +1,133 @@
package ghidra.app.plugin.core.decompile.actions;
import java.awt.Color;
import java.awt.event.KeyEvent;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.DecompilerController;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.HighlightToken;
import ghidra.app.decompiler.component.HighlightTokenObservableList;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.HighVariable;
import ghidra.util.HelpLocation;
import ghidra.util.UndefinedFunction;
public class SetHighlightAction extends DockingAction {
private DecompilerPanel panel;
private DecompilerController controller;
private int minColorSaturation = 100;
private int defaultColorAlpha = 100;
public SetHighlightAction(String owner, DecompilerController controller) {
this(owner, controller, false);
}
/**
* Initialize the menu items, hotkeys and set the option to enabled according
* to the decompileOptions
*/
public SetHighlightAction(String owner, DecompilerController controller, boolean isEnabled) {
super("Display.Custom Highlights", owner, false);
this.panel = controller.getDecompilerPanel();
this.controller = controller;
setPopupMenuData(new MenuData(new String[] { "Highlight Token" }, "Decompile"));
setKeyBindingData(new KeyBindingData(KeyEvent.VK_C, 0));
setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName()));
setEnabled(isEnabled);
}
/**
* Reset the highlighting accounting - This removes all highlighting.
*/
public void reset() {
if (panel != null) {
panel.getHighlightedTokens().clear();
panel.tokenHighlightsChanged();
}
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DecompilerActionContext)) {
return false;
}
Function function = controller.getFunction();
if (function instanceof UndefinedFunction) {
return false;
}
DecompilerActionContext decompilerActionContext = (DecompilerActionContext) context;
if (decompilerActionContext.isDecompiling()) {
return false;
}
ClangToken tokenAtCursor = panel.getTokenAtCursor();
if (tokenAtCursor == null) {
return false;
}
HighVariable variable = tokenAtCursor.getHighVariable();
tokenAtCursor.getSyntaxType();
if (variable == null) {
return false;
}
return true;
}
@Override
public boolean setEnabled(boolean newValue) {
if (newValue == false) {
panel.repaintHighlightTokens(true);
}
return super.setEnabled(newValue);
}
@Override
public void actionPerformed(ActionContext context) {
if (panel != null) {
ClangToken tokenAtCursor = panel.getTokenAtCursor();
HighlightTokenObservableList<HighlightToken> highlightTokens = panel.getHighlightedTokens();
Function func = controller.getDecompileData().getFunction();
Color highlightColor = getNewColor();
HighlightToken htoken = new HighlightToken(tokenAtCursor, highlightColor, func);
// If token was already highlighted (in the list), we remove it.
if (highlightTokens.remove(htoken)) {
panel.clearHighlights();
panel.setHighlightedTokens(highlightTokens);
panel.repaintHighlightTokens(true);
panel.tokenHighlightsChanged();
} else {
// Token was not in the list, this is a new request to set highlighting
highlightTokens.add(htoken);
panel.setHighlightedTokens(highlightTokens);
panel.addTokenHighlight(tokenAtCursor, highlightColor);
panel.tokenHighlightsChanged();
}
}
}
private Color getNewColor() {
return new Color((int)(minColorSaturation + Math.random() * (256 - minColorSaturation)),
(int)(minColorSaturation + Math.random() * (256 - minColorSaturation)),
(int)(minColorSaturation + Math.random() * (256 - minColorSaturation)), defaultColorAlpha);
}
}