GP-3494 - Decompiler - Updated the middle-mouse toggle to be persistent when moving the cursor; Added actions for navigating the middle-moused tokens

This commit is contained in:
dragonmacher 2024-06-18 14:32:00 -04:00
parent acb5b0eb64
commit 9f8b03a90f
26 changed files with 886 additions and 299 deletions

View File

@ -22,7 +22,6 @@ import java.util.stream.Collectors;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.theme.GColor;
import ghidra.app.decompiler.ClangSyntaxToken;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.*;
@ -120,7 +119,7 @@ public class DiffClangHighlightController extends LocationClangHighlightControll
List<ClangToken> tokens = addPrimaryHighlightToTokensForParenthesis(
(ClangSyntaxToken) tok, defaultParenColor);
reHighlightDiffs(tokens);
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
}
TokenBin tokenBin = null;

View File

@ -4370,7 +4370,8 @@
<title>Middle-Click</title>
<para>
Highlights every occurrence of a variable, constant, or operator represented by the selected
token, within the Decompiler window.
token, within the Decompiler window. There are actions available from the popup menu and from
the keyboard to navigate to each highlighted token.
</para>
</sect2>
@ -4770,6 +4771,17 @@
</para>
</sect2>
<sect2 id="GoToMiddleMouseHighlight">
<title>Go To Next/Previous Highlight</title>
<para>
These actions are available from the popup menu and keyboard. Only tokens highlighted from the
middle-mouse will be navigated. <emphasis role="bold">Shift-Comma</emphasis> will go to the
previous highlighted token. <emphasis role="bold">Shift-Period</emphasis> will go to the
next highlighted token. These key bindings can be changed via the
<link xlink:href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Tool Options Dialog</link>.
</para>
</sect2>
<sect2 id="ActionHighlight">
<title>Highlight</title>
<para>

View File

@ -533,8 +533,10 @@
<p>
Highlights every occurrence of a variable, constant, or operator represented by the selected
token, within the Decompiler window.
token, within the Decompiler window. There are actions available from the popup menu and from
the keyboard to navigate to each highlighted token.
</p>
</div>
</div>
@ -958,6 +960,19 @@
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="GoToMiddleMouseHighlight"></a>Go To Next/Previous Highlight</h3></div></div></div>
<p>
These actions are available from the popup menu and keyboard. Only tokens highlighted from the
middle-mouse will be navigated. <span class="bold"><strong>Shift-Comma</strong></span> will
go to the previous highlighted token. <span class="bold"><strong>Shift-Period</strong></span>
will go to the next highlighted token. These key bindings can be changed via the
<a class="ulink" href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option" target="_top">Tool Options Dialog</a>.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="ActionHighlight"></a>Highlight</h3></div></div></div>

View File

@ -25,7 +25,7 @@ import java.util.*;
*/
public class ClangLine {
private int indent_level;
private ArrayList<ClangToken> tokens;
private List<ClangToken> tokens;
private int lineNumber;
public ClangLine(int lineNumber, int indent) {
@ -35,7 +35,7 @@ public class ClangLine {
}
public String getIndentString() {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < indent_level; i++) {
buffer.append(PrettyPrinter.INDENT_STRING);
}
@ -51,7 +51,7 @@ public class ClangLine {
tok.setLineParent(this);
}
public ArrayList<ClangToken> getAllTokens() {
public List<ClangToken> getAllTokens() {
return tokens;
}
@ -95,7 +95,6 @@ public class ClangLine {
if (isCallout) {
buffy.append(end);
}
}
return buffy.toString();

View File

@ -37,7 +37,7 @@ public class PrettyPrinter {
private Function function;
private ClangTokenGroup tokgroup;
private ArrayList<ClangLine> lines = new ArrayList<>();
private List<ClangLine> lines = new ArrayList<>();
private NameTransformer transformer;
/**
@ -58,7 +58,7 @@ public class PrettyPrinter {
private void padEmptyLines() {
for (ClangLine line : lines) {
ArrayList<ClangToken> tokenList = line.getAllTokens();
List<ClangToken> tokenList = line.getAllTokens();
if (tokenList.size() == 0) {
ClangToken spacer = ClangToken.buildSpacer(null, line.getIndent(), INDENT_STRING);
spacer.setLineParent(line);
@ -72,11 +72,11 @@ public class PrettyPrinter {
}
/**
* Returns an array list of the C language lines contained in the
* Returns a list of the C language lines contained in the
* C language token group.
* @return an array list of the C language lines
* @return a list of the C language lines
*/
public ArrayList<ClangLine> getLines() {
public List<ClangLine> getLines() {
return lines;
}
@ -86,8 +86,7 @@ public class PrettyPrinter {
* @return a string of readable C code
*/
public DecompiledFunction print() {
StringBuffer buff = new StringBuffer();
StringBuilder buff = new StringBuilder();
for (ClangLine line : lines) {
buff.append(line.getIndentString());
List<ClangToken> tokens = line.getAllTokens();

View File

@ -15,8 +15,7 @@
*/
package ghidra.app.decompiler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
/**
* An iterator over ClangToken objects. The iterator walks a tree of ClangNode objects based on
@ -126,7 +125,7 @@ public class TokenIterator implements Iterator<ClangToken> {
* @param forward is true for a forward iterator, false for a backward iterator
*/
public TokenIterator(ClangToken token, boolean forward) {
ArrayList<ClangTokenGroup> groupList = new ArrayList<>();
List<ClangTokenGroup> groupList = new ArrayList<>();
ClangNode node = token.Parent();
while (node != null) {
groupList.add((ClangTokenGroup) node);
@ -154,7 +153,7 @@ public class TokenIterator implements Iterator<ClangToken> {
* @param forward is true for a forward iterator, false for a backward iterator
*/
public TokenIterator(ClangTokenGroup group, boolean forward) {
ArrayList<ClangTokenGroup> groupList = new ArrayList<>();
List<ClangTokenGroup> groupList = new ArrayList<>();
ClangNode node = group;
while (node instanceof ClangTokenGroup) {
ClangTokenGroup curGroup = (ClangTokenGroup) node;

View File

@ -19,6 +19,7 @@ import java.awt.Color;
import java.util.*;
import java.util.function.Supplier;
import generic.json.Json;
import ghidra.app.decompiler.*;
/**
@ -95,7 +96,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
}
Supplier<? extends Collection<ClangToken>> tokens = () -> highlights.keySet();
ColorProvider colorProvider = t -> highlights.get(t);
ColorProvider colorProvider = new MappedTokenColorProvider(highlights);
decompilerPanel.addHighlighterHighlights(this, tokens, colorProvider);
clones.forEach(c -> c.applyHighlights());
@ -155,6 +156,25 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
@Override
public String toString() {
return super.toString() + ' ' + id;
return Json.toString(this, "matcher", "id");
}
private class MappedTokenColorProvider implements ColorProvider {
private Map<ClangToken, Color> highlights;
MappedTokenColorProvider(Map<ClangToken, Color> highlights) {
this.highlights = highlights;
}
@Override
public Color getColor(ClangToken token) {
return highlights.get(token);
}
@Override
public String toString() {
return "Token Matcher Color " + matcher.toString();
}
}
}

View File

@ -20,8 +20,6 @@ import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.collections4.map.LazyMap;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
@ -42,7 +40,7 @@ import util.CollectionUtils;
*
* <p>This class maintains the following types of highlights:
* <UL>
* <LI>Primary Highlights - triggered by user clicking and some user actions; considered transient
* <LI> Context Highlights - triggered by user clicking and some user actions; considered transient
* and get cleared whenever the location changes. These highlights show state such as the
* current field, impact of a variable (via a slicing action), or related syntax (such as
* matching braces)
@ -77,21 +75,8 @@ public abstract class ClangHighlightController {
protected Color defaultHighlightColor = DEFAULT_HIGHLIGHT_COLOR;
protected Color defaultParenColor = DEFAULT_HIGHLIGHT_COLOR;
private TokenHighlights primaryHighlightTokens = new TokenHighlights();
private Map<Function, List<ClangDecompilerHighlighter>> secondaryHighlightersbyFunction =
LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>());
// store the secondary highlighters here in addition to the map below so that we may discern
// between secondary highlights and highlight service highlights
private Set<ClangDecompilerHighlighter> secondaryHighlighters = new HashSet<>();
// all highlighters, including secondary and highlight service highlighters
private Map<ClangDecompilerHighlighter, TokenHighlights> highlighterHighlights =
new HashMap<>();
// color supplier for secondary highlights
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
private TokenHighlights contextHighlightTokens = new TokenHighlights();
private UserHighlights userHighlights = new UserHighlights();
/**
* A counter to track updates so that clients know when a buffered highlight request is invalid
@ -113,21 +98,8 @@ public abstract class ClangHighlightController {
* color.
* @return the color provider
*/
public ColorProvider getRandomColorProvider() {
return token -> secondaryHighlightColors.getColor(token.getText());
}
/**
* Returns the token that has the primary highlight applied, if any. If multiple tokens are
* highlighted, then the return value is arbitrary.
* @return the highlighted text
*/
public String getPrimaryHighlightedText() {
ClangToken highlightedToken = getHighlightedToken();
if (highlightedToken != null) {
return highlightedToken.getText();
}
return null;
public ColorProvider getGeneratedColorProvider() {
return new GeneratedColorProvider();
}
/**
@ -139,8 +111,8 @@ public abstract class ClangHighlightController {
return updateId;
}
public boolean hasPrimaryHighlight(ClangToken token) {
return primaryHighlightTokens.contains(token);
public boolean hasContextHighlight(ClangToken token) {
return contextHighlightTokens.contains(token);
}
public boolean hasSecondaryHighlight(ClangToken token) {
@ -148,26 +120,19 @@ public abstract class ClangHighlightController {
}
public boolean hasSecondaryHighlights(Function function) {
return !secondaryHighlightersbyFunction.get(function).isEmpty();
return userHighlights.hasSecondaryHighlights(function);
}
public Color getSecondaryHighlight(ClangToken token) {
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
if (highlighter != null) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
return hlToken.getColor();
}
return null;
return userHighlights.getSecondaryHighlight(token);
}
public TokenHighlightColors getSecondaryHighlightColors() {
return secondaryHighlightColors;
return userHighlights.getSecondaryHighlightColors();
}
public TokenHighlights getPrimaryHighlights() {
return primaryHighlightTokens;
return contextHighlightTokens;
}
/**
@ -177,8 +142,8 @@ public abstract class ClangHighlightController {
* @param function the function
* @return the highlighters
*/
public Set<ClangDecompilerHighlighter> getSecondaryHighlighters(Function function) {
return new HashSet<>(secondaryHighlightersbyFunction.get(function));
public Set<DecompilerHighlighter> getSecondaryHighlighters(Function function) {
return userHighlights.getSecondaryHighlighters(function);
}
/**
@ -187,11 +152,8 @@ public abstract class ClangHighlightController {
* function-specific.
* @return the highlighters
*/
public Set<ClangDecompilerHighlighter> getGlobalHighlighters() {
Set<ClangDecompilerHighlighter> allHighlighters = highlighterHighlights.keySet();
Set<ClangDecompilerHighlighter> results = new HashSet<>(allHighlighters);
results.removeAll(secondaryHighlighters);
return results;
public Set<DecompilerHighlighter> getGlobalHighlighters() {
return userHighlights.getGlobalHighlighters();
}
/**
@ -201,7 +163,7 @@ public abstract class ClangHighlightController {
* @see #getPrimaryHighlights()
*/
public TokenHighlights getHighlighterHighlights(DecompilerHighlighter highlighter) {
return highlighterHighlights.get(highlighter);
return userHighlights.getHighlights(highlighter);
}
/**
@ -209,8 +171,8 @@ public abstract class ClangHighlightController {
* @return token or null
*/
public ClangToken getHighlightedToken() {
if (primaryHighlightTokens.size() == 1) {
HighlightToken hlToken = CollectionUtils.any(primaryHighlightTokens);
if (contextHighlightTokens.size() == 1) {
HighlightToken hlToken = CollectionUtils.any(contextHighlightTokens);
return hlToken.getToken();
}
return null;
@ -236,7 +198,7 @@ public abstract class ClangHighlightController {
updateHighlightColor(token);
};
doClearHighlights(primaryHighlightTokens, clearAll);
doClearHighlights(contextHighlightTokens, clearAll);
notifyListeners();
}
@ -262,10 +224,9 @@ public abstract class ClangHighlightController {
* @param tokens the tokens
*/
public void togglePrimaryHighlights(Color hlColor, Supplier<List<ClangToken>> tokens) {
boolean isAllHighlighted = true;
for (ClangToken otherToken : tokens.get()) {
if (!hasPrimaryHighlight(otherToken)) {
for (ClangToken token : tokens.get()) {
if (!hasContextHighlight(token)) {
isAllHighlighted = false;
break;
}
@ -276,8 +237,7 @@ public abstract class ClangHighlightController {
clearPrimaryHighlights();
if (isAllHighlighted) {
// nothing to do; we toggled from 'all on' to 'all off'
return;
return; // nothing to do; we toggled from 'all on' to 'all off'
}
addPrimaryHighlights(tokens, hlColor);
@ -289,9 +249,11 @@ public abstract class ClangHighlightController {
*/
public void removeSecondaryHighlights(Function f) {
List<ClangDecompilerHighlighter> highlighters = secondaryHighlightersbyFunction.get(f);
for (ClangDecompilerHighlighter highlighter : highlighters) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
List<DecompilerHighlighter> highlighters =
userHighlights.getSecondaryHighlightersByFunction(f);
for (DecompilerHighlighter highlighter : highlighters) {
TokenHighlights highlights = userHighlights.getHighlights(highlighter);
Consumer<ClangToken> clearHighlight = token -> updateHighlightColor(token);
doClearHighlights(highlights, clearHighlight);
}
@ -305,38 +267,16 @@ public abstract class ClangHighlightController {
* @see #removeSecondaryHighlights(Function)
*/
public void removeSecondaryHighlights(ClangToken token) {
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
DecompilerHighlighter highlighter = userHighlights.getSecondaryHighlighter(token);
if (highlighter != null) {
highlighter.dispose(); // this will call removeHighlighterHighlights()
}
notifyListeners();
}
private DecompilerHighlighter getSecondaryHighlighter(ClangToken token) {
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
if (hlToken != null) {
return highlighter;
}
}
return null;
}
public void removeHighlighter(DecompilerHighlighter highlighter) {
removeHighlighterHighlights(highlighter);
highlighterHighlights.remove(highlighter);
secondaryHighlighters.remove(highlighter);
Collection<List<ClangDecompilerHighlighter>> lists =
secondaryHighlightersbyFunction.values();
for (List<ClangDecompilerHighlighter> highlighters : lists) {
if (highlighters.remove(highlighter)) {
break;
}
}
userHighlights.remove(highlighter);
}
/**
@ -345,7 +285,7 @@ public abstract class ClangHighlightController {
*/
public void removeHighlighterHighlights(DecompilerHighlighter highlighter) {
TokenHighlights highlighterTokens = highlighterHighlights.get(highlighter);
TokenHighlights highlighterTokens = userHighlights.get(highlighter);
if (highlighterTokens == null) {
return;
}
@ -361,59 +301,56 @@ public abstract class ClangHighlightController {
* @param function the function
* @param highlighter the highlighter
*/
public void addSecondaryHighlighter(Function function, ClangDecompilerHighlighter highlighter) {
// Note: this highlighter has likely already been added the the this class, but has not
// yet been bound to the given function.
secondaryHighlightersbyFunction.get(function).add(highlighter);
secondaryHighlighters.add(highlighter);
highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
public void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) {
userHighlights.addSecondaryHighlighter(function, highlighter);
}
// Note: this is used for all highlight types, secondary and highlighter service highlighters
public void addHighlighter(ClangDecompilerHighlighter highlighter) {
highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
userHighlights.add(highlighter);
}
// Note: this is used for all highlight types, secondary and highlighter service highlights
public void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
Supplier<? extends Collection<ClangToken>> tokens,
ColorProvider colorProvider) {
public void addHighlighterHighlights(DecompilerHighlighter highlighter,
Supplier<? extends Collection<ClangToken>> tokens, ColorProvider colorProvider) {
Objects.requireNonNull(highlighter);
TokenHighlights highlighterTokens =
highlighterHighlights.computeIfAbsent(highlighter, k -> new TokenHighlights());
TokenHighlights highlighterTokens = userHighlights.add(highlighter);
addTokensToHighlights(tokens.get(), colorProvider, highlighterTokens);
}
private void addPrimaryHighlights(Supplier<? extends Collection<ClangToken>> tokens,
Color hlColor) {
ColorProvider colorProvider = token -> hlColor;
addTokensToHighlights(tokens.get(), colorProvider, primaryHighlightTokens);
addPrimaryHighlights(tokens.get(), hlColor);
}
private void addPrimaryHighlights(Collection<ClangToken> tokens, Color hlColor) {
ColorProvider colorProvider = new DefaultColorProvider("Tokens Highlight Color", hlColor);
addTokensToHighlights(tokens, colorProvider, contextHighlightTokens);
}
public void addPrimaryHighlights(ClangNode parentNode, Set<PcodeOp> ops, Color hlColor) {
addPrimaryHighlights(parentNode, token -> {
PcodeOp op = token.getPcodeOp();
return ops.contains(op) ? hlColor : null;
});
ColorProvider colorProvider = new DefaultColorProvider("PcodeOp Highlight Color", hlColor) {
@Override
public Color getColor(ClangToken token) {
PcodeOp op = token.getPcodeOp();
return ops.contains(op) ? hlColor : null;
}
};
addPrimaryHighlights(parentNode, colorProvider);
}
public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
Set<ClangToken> tokens = new HashSet<>();
gatherAllTokens(parentNode, tokens);
addTokensToHighlights(tokens, colorProvider::getColor, primaryHighlightTokens);
addTokensToHighlights(tokens, colorProvider, contextHighlightTokens);
}
private void addPrimaryHighlights(Collection<ClangToken> tokens, Color hlColor) {
ColorProvider colorProvider = token -> hlColor;
addTokensToHighlights(tokens, colorProvider, primaryHighlightTokens);
}
private void addTokensToHighlights(Collection<ClangToken> tokens,
ColorProvider colorProvider, TokenHighlights currentHighlights) {
private void addTokensToHighlights(Collection<ClangToken> tokens, ColorProvider colorProvider,
TokenHighlights currentHighlights) {
updateId++;
@ -441,7 +378,7 @@ public abstract class ClangHighlightController {
}
private void updateHighlightColor(ClangToken t) {
// set the color to the current combined value of both highlight types
// set the color to the current combined value of all highlight types
Color combinedColor = getCombinedColor(t);
t.setHighlight(combinedColor);
}
@ -469,7 +406,7 @@ public abstract class ClangHighlightController {
// note: not sure whether we should always blend all colors or decide to allow some
// highlighters have precedence for highlighting
HighlightToken primaryHl = primaryHighlightTokens.get(t);
HighlightToken primaryHl = contextHighlightTokens.get(t);
Color blendedHlColor = blendHighlighterColors(t);
List<Color> allColors = new ArrayList<>();
@ -506,12 +443,12 @@ public abstract class ClangHighlightController {
return null; // not sure if this can happen
}
Set<ClangDecompilerHighlighter> global = getGlobalHighlighters();
Set<ClangDecompilerHighlighter> secondary = getSecondaryHighlighters(function);
Iterable<ClangDecompilerHighlighter> it = CollectionUtils.asIterable(global, secondary);
Set<DecompilerHighlighter> global = getGlobalHighlighters();
Set<DecompilerHighlighter> secondary = getSecondaryHighlighters(function);
Iterable<DecompilerHighlighter> it = CollectionUtils.asIterable(global, secondary);
Color lastColor = null;
for (ClangDecompilerHighlighter highlighter : it) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
for (DecompilerHighlighter highlighter : it) {
TokenHighlights highlights = userHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
if (hlToken == null) {
continue;
@ -542,6 +479,24 @@ public abstract class ClangHighlightController {
return highFunction.getFunction();
}
protected void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token,
Color highlightColor) {
if (DecompilerUtils.isBrace(token)) {
highlightBrace(token, highlightColor);
notifyListeners();
}
}
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
if (matchingBrace != null) {
matchingBrace.setMatchingToken(true); // this is a signal to the painter
addPrimaryHighlights(Set.of(matchingBrace), highlightColor);
}
}
/**
* If input token is a parenthesis, highlight all tokens between it and its match
* @param tok potential parenthesis token
@ -609,23 +564,6 @@ public abstract class ClangHighlightController {
return results;
}
public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
if (DecompilerUtils.isBrace(token)) {
highlightBrace(token, highlightColor);
notifyListeners();
}
}
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
if (matchingBrace != null) {
matchingBrace.setMatchingToken(true); // this is a signal to the painter
addPrimaryHighlights(Set.of(matchingBrace), highlightColor);
}
}
public void addListener(ClangHighlightListener listener) {
listeners.add(listener);
}
@ -642,9 +580,21 @@ public abstract class ClangHighlightController {
public void dispose() {
listeners.clear();
primaryHighlightTokens.clear();
secondaryHighlighters.clear();
secondaryHighlightersbyFunction.clear();
highlighterHighlights.clear();
contextHighlightTokens.clear();
userHighlights.dispose();
}
private class GeneratedColorProvider implements ColorProvider {
@Override
public Color getColor(ClangToken token) {
return userHighlights.getSecondaryColor(token.getText());
}
@Override
public String toString() {
return "Generated Color Provider " + userHighlights.getAppliedColorsString();
}
}
}

View File

@ -39,8 +39,6 @@ import ghidra.program.model.pcode.HighFunction;
import ghidra.util.Msg;
/**
*
*
* Control the GUI layout for displaying tokenized C code
*/
public class ClangLayoutController implements LayoutModel, LayoutModelListener {
@ -53,10 +51,10 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
private Field[] fieldList; // Array of fields comprising layout
private FontMetrics metrics;
private FieldHighlightFactory hlFactory;
private ArrayList<LayoutModelListener> listeners;
private List<LayoutModelListener> listeners;
private Color[] syntaxColor; // Foreground colors.
private BigInteger numIndexes = BigInteger.ZERO;
private ArrayList<ClangLine> lines = new ArrayList<>();
private List<ClangLine> lines = new ArrayList<>();
private boolean showLineNumbers = true;
@ -71,7 +69,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
buildLayouts(null, null, null, false);
}
public ArrayList<ClangLine> getLines() {
public List<ClangLine> getLines() {
return lines;
}
@ -248,7 +246,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
}
}
private void splitToMaxWidthLines(ArrayList<String> res, String line) {
private void splitToMaxWidthLines(List<String> res, String line) {
int maxchar;
if ((maxWidth == 0) || (indentWidth == 0)) {
maxchar = 40;
@ -257,7 +255,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
maxchar = maxWidth / indentWidth;
}
String[] toklist = line.split("[ \t]+");
StringBuffer buf = new StringBuffer();
StringBuilder buf = new StringBuilder();
int cursize = 0;
boolean atleastone = false;
int i = 0;
@ -275,7 +273,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
res.add(finishLine);
cursize = 5;
atleastone = false;
buf = new StringBuffer();
buf = new StringBuilder();
buf.append(" ");
}
else {
@ -302,7 +300,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return false; // No error message to add
}
String[] errlines_init = errmsg.split("[\n\r]+");
ArrayList<String> errlines = new ArrayList<>();
List<String> errlines = new ArrayList<>();
for (String element : errlines_init) {
splitToMaxWidthLines(errlines, element);
}
@ -345,16 +343,16 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
java.util.function.Function<String, SearchMatch> matcher, String searchString,
FieldLocation currentLocation) {
int row = currentLocation.getIndex().intValue();
for (int i = row; i < fieldList.length; i++) {
ClangTextField field = (ClangTextField) fieldList[i];
String partialLine =
getTextLineFromOffset((i == row) ? currentLocation : null, field, true);
SearchMatch match = matcher.apply(partialLine);
int startRow = currentLocation.getIndex().intValue();
for (int row = startRow; row < fieldList.length; row++) {
ClangTextField field = (ClangTextField) fieldList[row];
FieldLocation location = (row == startRow) ? currentLocation : null;
String lineText = getLineTextFromOffset(location, field, true);
SearchMatch match = matcher.apply(lineText);
if (match == SearchMatch.NO_MATCH) {
continue;
}
if (i == row) { // cursor is on this line
if (row == startRow) { // cursor is on this line
//
// The match start for all lines without the cursor will be relative to the start
// of the line, which is 0. However, when searching on the row with the cursor,
@ -362,13 +360,15 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
// compensate for the difference between the start of the line and the cursor.
//
String fullLine = field.getText();
int cursorOffset = fullLine.length() - partialLine.length();
int cursorOffset = fullLine.length() - lineText.length();
match.start += cursorOffset;
match.end += cursorOffset;
}
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
// we use 0 here because currently there is only one field, which is the entire line
int fieldNum = 0;
int column = getScreenColumnFromOffset(match.start, field);
FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column);
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, true);
@ -380,17 +380,19 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
java.util.function.Function<String, SearchMatch> matcher, String searchString,
FieldLocation currentLocation) {
int row = currentLocation.getIndex().intValue();
for (int i = row; i >= 0; i--) {
ClangTextField field = (ClangTextField) fieldList[i];
String textLine =
getTextLineFromOffset((i == row) ? currentLocation : null, field, false);
int startRow = currentLocation.getIndex().intValue();
for (int row = startRow; row >= 0; row--) {
ClangTextField field = (ClangTextField) fieldList[row];
FieldLocation location = (row == startRow) ? currentLocation : null;
String lineText = getLineTextFromOffset(location, field, false);
SearchMatch match = matcher.apply(textLine);
SearchMatch match = matcher.apply(lineText);
if (match != SearchMatch.NO_MATCH) {
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
// we use 0 here because currently there is only one field, which is the entire line
int fieldNum = 0;
int column = getScreenColumnFromOffset(match.start, field);
FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column);
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, false);
@ -483,7 +485,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return findNextTokenGoingBackward(function, searchString, currentLocation);
}
private String getTextLineFromOffset(FieldLocation location, ClangTextField textField,
private String getLineTextFromOffset(FieldLocation location, ClangTextField textField,
boolean forwardSearch) {
if (location == null) { // the cursor location is not on this line; use all of the text
@ -494,32 +496,28 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return "";
}
String partialText = textField.getText();
String lineText = textField.getText();
if (forwardSearch) {
int nextCol = location.getCol();
// protects against the location column being out of range (this can happen if we're
// searching forward and the cursor is past the last token)
if (nextCol >= partialText.length()) {
if (nextCol >= lineText.length()) {
return "";
}
// skip a character to start the next search; this prevents matching the previous match
return partialText.substring(nextCol);
return lineText.substring(nextCol);
}
// backwards search
return partialText.substring(0, location.getCol());
return lineText.substring(0, location.getCol());
}
private FieldNumberColumnPair getFieldIndexFromOffset(int screenOffset,
ClangTextField textField) {
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset);
// we use 0 here because currently there is only one field, which is the entire line
return new FieldNumberColumnPair(0, rowColLocation.col());
private int getScreenColumnFromOffset(int textOffset, ClangTextField textField) {
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(textOffset);
return rowColLocation.col();
}
private static class SearchMatch {
@ -565,27 +563,4 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
public void flushChanges() {
// nothing to do
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class FieldNumberColumnPair {
private final int fieldNumber;
private final int column;
FieldNumberColumnPair(int fieldNumber, int column) {
this.fieldNumber = fieldNumber;
this.column = column;
}
int getFieldNumber() {
return fieldNumber;
}
int getColumn() {
return column;
}
}
}

View File

@ -18,8 +18,8 @@ package ghidra.app.decompiler.component;
import java.util.List;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.FieldHighlightFactory;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.decompiler.ClangToken;
public class ClangTextField extends WrappingVerticalLayoutTextField {
@ -123,4 +123,12 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
public int getLineNumber() {
return lineNumber;
}
public ClangToken getFirstToken() {
return tokenList.get(0);
}
public ClangToken getLastToken() {
return tokenList.get(tokenList.size() - 1);
}
}

View File

@ -80,7 +80,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private FieldHighlightFactory hlFactory;
private ClangHighlightController highlightController;
private Map<String, ClangDecompilerHighlighter> highlightersById = new HashMap<>();
private Map<String, DecompilerHighlighter> highlightersById = new HashMap<>();
private PendingHighlightUpdate pendingHighlightUpdate;
private SwingUpdateManager highlighCursorUpdater = new SwingUpdateManager(() -> {
if (pendingHighlightUpdate != null) {
@ -89,6 +89,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
});
private ActiveMiddleMouse activeMiddleMouse;
private int middleMouseHighlightButton;
private Color middleMouseHighlightColor;
private Color currentVariableHighlightColor;
@ -194,7 +195,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return highlightController.getHighlighterHighlights(highligter);
}
private Set<ClangDecompilerHighlighter> getSecondaryHighlihgtersByFunction(Function function) {
public TokenHighlights getMiddleMouseHighlights() {
if (activeMiddleMouse != null) {
return activeMiddleMouse.getHighlights();
}
return null;
}
private Set<DecompilerHighlighter> getSecondaryHighlihgtersByFunction(Function function) {
return highlightController.getSecondaryHighlighters(function);
}
@ -212,31 +220,50 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public void addSecondaryHighlight(ClangToken token) {
ColorProvider cp = highlightController.getRandomColorProvider();
ColorProvider cp = highlightController.getGeneratedColorProvider();
addSecondaryHighlight(token.getText(), cp);
}
public void addSecondaryHighlight(ClangToken token, Color color) {
ColorProvider cp = t -> color;
ColorProvider cp = new DefaultColorProvider("User Secondary Highlight", color);
addSecondaryHighlight(token.getText(), cp);
}
private void addSecondaryHighlight(String tokenText, ColorProvider colorProvider) {
NameTokenMatcher matcher = new NameTokenMatcher(tokenText, colorProvider);
ClangDecompilerHighlighter highlighter = createHighlighter(matcher);
DecompilerHighlighter highlighter = createHighlighter(matcher);
applySecondaryHighlights(highlighter);
}
private void applySecondaryHighlights(ClangDecompilerHighlighter highlighter) {
private void applySecondaryHighlights(DecompilerHighlighter highlighter) {
Function function = decompileData.getFunction();
highlightController.addSecondaryHighlighter(function, highlighter);
highlighter.applyHighlights();
}
private void togglePrimaryHighlight(FieldLocation location, Field field, Color highlightColor) {
private void toggleMiddleMouseHighlight(FieldLocation location, Field field) {
ClangToken token = ((ClangTextField) field).getToken(location);
Supplier<List<ClangToken>> lazyTokens = () -> findTokensByName(token.getText());
highlightController.togglePrimaryHighlights(middleMouseHighlightColor, lazyTokens);
ColorProvider cp = new MiddleMouseColorProvider();
NameTokenMatcher matcher = new NameTokenMatcher(token.getText(), cp);
ActiveMiddleMouse previousMiddleMouse = activeMiddleMouse;
activeMiddleMouse = null;
if (previousMiddleMouse != null) {
// middle mousing always clears the last middle-mouse highlight
previousMiddleMouse.clear();
if (previousMiddleMouse.matches(token)) {
// middle mousing on the same token clears, but does not create a new highlight
return;
}
}
DecompilerHighlighter newMiddleMouseHighlighter = createHighlighter(matcher);
ActiveMiddleMouse newMiddleMouse = new ActiveMiddleMouse(token, newMiddleMouseHighlighter);
newMiddleMouse.apply();
activeMiddleMouse = newMiddleMouse;
}
void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
@ -248,14 +275,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
highlightController.removeHighlighterHighlights(highlighter);
}
public ClangDecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
public DecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
UUID uuId = UUID.randomUUID();
String id = uuId.toString();
return createHighlighter(id, tm);
}
public ClangDecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
ClangDecompilerHighlighter currentHighlighter = highlightersById.get(id);
public DecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
DecompilerHighlighter currentHighlighter = highlightersById.get(id);
if (currentHighlighter != null) {
currentHighlighter.dispose();
}
@ -271,7 +298,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
void removeHighlighter(String id) {
ClangDecompilerHighlighter highlighter = highlightersById.remove(id);
DecompilerHighlighter highlighter = highlightersById.remove(id);
highlightController.removeHighlighter(highlighter);
}
@ -339,11 +366,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private void cloneGlobalHighlighters(DecompilerPanel sourcePanel) {
Set<ClangDecompilerHighlighter> globalHighlighters =
Set<DecompilerHighlighter> globalHighlighters =
sourcePanel.highlightController.getGlobalHighlighters();
for (ClangDecompilerHighlighter otherHighlighter : globalHighlighters) {
for (DecompilerHighlighter otherHighlighter : globalHighlighters) {
ClangDecompilerHighlighter newHighlighter = otherHighlighter.clone(this);
if (!(otherHighlighter instanceof ClangDecompilerHighlighter clangHighlighter)) {
continue;
}
DecompilerHighlighter newHighlighter = clangHighlighter.clone(this);
highlightersById.put(newHighlighter.getId(), newHighlighter);
TokenHighlights otherHighlighterTokens =
@ -374,15 +405,20 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
// clone will match the cloned decompiler.
//
Function function = decompileData.getFunction();
Set<ClangDecompilerHighlighter> secondaryHighlighters =
Set<DecompilerHighlighter> secondaryHighlighters =
sourcePanel.getSecondaryHighlihgtersByFunction(function);
//
// We do NOT clone the secondary highlighters. This allows the user the remove them
// from the primary provider without effecting the cloned provider and vice versa.
//
for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
ClangDecompilerHighlighter newHighlighter = highlighter.copy(this);
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
if (!(highlighter instanceof ClangDecompilerHighlighter clangHighlighter)) {
continue;
}
DecompilerHighlighter newHighlighter = clangHighlighter.copy(this);
highlightersById.put(newHighlighter.getId(), newHighlighter);
applySecondaryHighlights(newHighlighter);
}
@ -455,9 +491,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
Set<ClangDecompilerHighlighter> globalHighlighters =
highlightController.getGlobalHighlighters();
for (ClangDecompilerHighlighter highlighter : globalHighlighters) {
Set<DecompilerHighlighter> globalHighlighters = highlightController.getGlobalHighlighters();
for (DecompilerHighlighter highlighter : globalHighlighters) {
highlighter.clearHighlights();
highlighter.applyHighlights();
}
@ -470,9 +505,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
Set<ClangDecompilerHighlighter> secondaryHighlighters =
Set<DecompilerHighlighter> secondaryHighlighters =
getSecondaryHighlihgtersByFunction(function);
for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
highlighter.clearHighlights();
highlighter.applyHighlights();
}
@ -719,7 +754,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
if (buttonState == middleMouseHighlightButton && clickCount == 1) {
togglePrimaryHighlight(location, field, middleMouseHighlightColor);
toggleMiddleMouseHighlight(location, field);
}
}
@ -770,19 +805,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
controller.goToFunction(function, newWindow);
return;
}
// TODO no idea what this is supposed to be handling...someone doc this please
// String labelName = functionToken.getText();
// if (labelName.startsWith("func_0x")) {
// try {
// Address addr =
// decompileData.getFunction().getEntryPoint().getAddress(labelName.substring(7));
// controller.goToAddress(addr, newWindow);
// }
// catch (AddressFormatException e) {
// controller.goToLabel(labelName, newWindow);
// }
// }
}
private void tryGoToLabel(ClangLabelToken token, boolean newWindow) {
@ -1377,4 +1399,57 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
}
}
private class MiddleMouseColorProvider implements ColorProvider {
@Override
public Color getColor(ClangToken token) {
return middleMouseHighlightColor;
}
@Override
public String toString() {
return "Middle Mouse Color Provider " + middleMouseHighlightColor;
}
}
/**
* A class to track the current middle moused token.
*/
private class ActiveMiddleMouse {
private ClangToken token;
private DecompilerHighlighter highlighter;
ActiveMiddleMouse(ClangToken token, DecompilerHighlighter highlighter) {
this.token = token;
this.highlighter = highlighter;
}
TokenHighlights getHighlights() {
return highlightController.getHighlighterHighlights(highlighter);
}
DecompilerHighlighter getHighlighter() {
return highlighter;
}
boolean matches(ClangToken other) {
return token.getText().equals(other.getText());
}
void clear() {
highlightController.removeHighlighter(highlighter);
}
void apply() {
applySecondaryHighlights(highlighter);
}
@Override
public String toString() {
return "Middle Mouse Token " + token;
}
}
}

View File

@ -33,9 +33,10 @@ import ghidra.program.model.pcode.*;
public class DecompilerUtils {
/**
* Gaither decompiler options from tool and program. If tool is null or does not provide
* Gather decompiler options from tool and program. If tool is null or does not provide
* a {@link OptionsService} provider only options stored within the program will be consumed.
* @param serviceProvider plugin tool or service provider providing access to {@link OptionsService}
* @param serviceProvider plugin tool or service provider providing access to
* {@link OptionsService}
* @param program program
* @return decompiler options
*/
@ -281,7 +282,7 @@ public class DecompilerUtils {
* @param function decompiled function
* @return true if {@code var} corresponds to existing auto {@code this} parameter, else false
*/
public static boolean testForAutoParameterThis(HighVariable var, Function function) {
public static boolean isThisParameter(HighVariable var, Function function) {
if (var instanceof HighParam) {
int slot = ((HighParam) var).getSlot();
Parameter parameter = function.getParameter(slot);
@ -626,7 +627,7 @@ public class DecompilerUtils {
String destinationStart = label.getText() + ':';
Address address = label.getMinAddress();
List<ClangToken> tokens = DecompilerUtils.getTokens(root, address);
List<ClangToken> tokens = getTokens(root, address);
for (ClangToken token : tokens) {
if (isGoToStatement(token)) {
continue; // ignore any goto statements
@ -791,7 +792,7 @@ public class DecompilerUtils {
* @param group is the token hierarchy
* @return the array of ClangLine objects
*/
public static ArrayList<ClangLine> toLines(ClangTokenGroup group) {
public static List<ClangLine> toLines(ClangTokenGroup group) {
List<ClangNode> alltoks = new ArrayList<>();
group.flatten(alltoks);

View File

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.decompiler.component;
import java.awt.Color;
import ghidra.app.decompiler.ClangToken;
/**
* A color provider that returns a specific color.
*/
public class DefaultColorProvider implements ColorProvider {
private Color color;
private String prefix;
/**
* Constructor
* @param prefix a descriptive prefix used in the {@link #toString()} method
* @param color the color
*/
DefaultColorProvider(String prefix, Color color) {
this.prefix = prefix;
this.color = color;
}
@Override
public Color getColor(ClangToken token) {
return color;
}
@Override
public String toString() {
return prefix + ' ' + color;
}
}

View File

@ -50,7 +50,7 @@ public class LocationClangHighlightController extends ClangHighlightController {
addPrimaryHighlight(tok, defaultHighlightColor);
if (tok instanceof ClangSyntaxToken) {
addPrimaryHighlightToTokensForParenthesis((ClangSyntaxToken) tok, defaultParenColor);
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
}
}
}

View File

@ -17,6 +17,7 @@ package ghidra.app.decompiler.component;
import java.awt.Color;
import generic.json.Json;
import ghidra.app.decompiler.CTokenHighlightMatcher;
import ghidra.app.decompiler.ClangToken;
@ -40,4 +41,9 @@ class NameTokenMatcher implements CTokenHighlightMatcher {
}
return null;
}
@Override
public String toString() {
return Json.toString(this);
}
}

View File

@ -35,11 +35,6 @@ public class NullClangHighlightController extends ClangHighlightController {
// stub
}
@Override
public String getPrimaryHighlightedText() {
return null;
}
@Override
public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
// stub
@ -51,7 +46,7 @@ public class NullClangHighlightController extends ClangHighlightController {
}
@Override
public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
public void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token, Color highlightColor) {
// stub
}

View File

@ -25,7 +25,7 @@ import generic.theme.Gui;
*/
public class TokenHighlightColors {
private Map<String, Color> colorsByName = new HashMap<>();
private Map<String, Color> colorsByText = new HashMap<>();
private List<Color> recentColors = new ArrayList<>();
private Color generateColor() {
@ -42,15 +42,27 @@ public class TokenHighlightColors {
}
public Color getColor(String text) {
return colorsByName.computeIfAbsent(text, t -> generateColor());
return colorsByText.computeIfAbsent(text, t -> generateColor());
}
public void setColor(String text, Color color) {
colorsByName.put(text, color);
colorsByText.put(text, color);
recentColors.add(color);
}
public List<Color> getRecentColors() {
return recentColors;
}
public String getAppliedColorsString() {
if (colorsByText.isEmpty()) {
return "No tokens highlighted";
}
return colorsByText.toString();
}
@Override
public String toString() {
return getAppliedColorsString();
}
}

View File

@ -0,0 +1,148 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.decompiler.component;
import java.awt.Color;
import java.util.*;
import org.apache.commons.collections4.map.LazyMap;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.DecompilerHighlighter;
import ghidra.program.model.listing.Function;
/**
* A class to manage and track Decompiler highlights created by the user via the UI or from a
* script. This class manages secondary and global highlights. For a description of these terms,
* see {@link ClangHighlightController}.
* <p>
* These highlights will remain until cleared explicitly by the user or a client API call.
* Contrastingly, context highlights are cleared as the user moves the cursor around the Decompiler
* display.
*/
public class UserHighlights {
private Map<Function, List<DecompilerHighlighter>> secondaryHighlightersByFunction =
LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>());
// store the secondary highlighters here in addition to the map below so that we may discern
// between secondary highlights and highlight service highlights
private Set<DecompilerHighlighter> secondaryHighlighters = new HashSet<>();
// all highlighters, including secondary and global highlight service highlighters
private Map<DecompilerHighlighter, TokenHighlights> allHighlighterHighlights = new HashMap<>();
// color supplier for secondary highlights
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
Color getSecondaryColor(String text) {
// Note: this call is used to generate colors for secondary highlighters that this API
// creates. Client highlighters will create their own colors.
return secondaryHighlightColors.getColor(text);
}
String getAppliedColorsString() {
return secondaryHighlightColors.getAppliedColorsString();
}
boolean hasSecondaryHighlights(Function function) {
return !secondaryHighlightersByFunction.get(function).isEmpty();
}
Color getSecondaryHighlight(ClangToken token) {
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
if (highlighter != null) {
TokenHighlights highlights = allHighlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
return hlToken.getColor();
}
return null;
}
TokenHighlightColors getSecondaryHighlightColors() {
return secondaryHighlightColors;
}
Set<DecompilerHighlighter> getSecondaryHighlighters(Function function) {
return new HashSet<>(secondaryHighlightersByFunction.get(function));
}
Set<DecompilerHighlighter> getGlobalHighlighters() {
Set<DecompilerHighlighter> allHighlighters = allHighlighterHighlights.keySet();
Set<DecompilerHighlighter> results = new HashSet<>(allHighlighters);
results.removeAll(secondaryHighlighters);
return results;
}
List<DecompilerHighlighter> getSecondaryHighlightersByFunction(Function f) {
return secondaryHighlightersByFunction.get(f);
}
TokenHighlights getHighlights(DecompilerHighlighter highlighter) {
return allHighlighterHighlights.get(highlighter);
}
DecompilerHighlighter getSecondaryHighlighter(ClangToken token) {
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
TokenHighlights highlights = allHighlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
if (hlToken != null) {
return highlighter;
}
}
return null;
}
void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) {
// Note: this highlighter has likely already been added the the this class, but has not
// yet been bound to the given function.
secondaryHighlightersByFunction.get(function).add(highlighter);
secondaryHighlighters.add(highlighter);
allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
}
// This adds the given highlighter. This is for global and secondary highlights. Secondary
// highlights will be later registered to this class for the function they apply to.
TokenHighlights add(DecompilerHighlighter highlighter) {
allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
return allHighlighterHighlights.get(highlighter);
}
void remove(DecompilerHighlighter highlighter) {
allHighlighterHighlights.remove(highlighter);
secondaryHighlighters.remove(highlighter);
Collection<List<DecompilerHighlighter>> lists = secondaryHighlightersByFunction.values();
for (List<DecompilerHighlighter> highlighters : lists) {
if (highlighters.remove(highlighter)) {
break;
}
}
}
TokenHighlights get(DecompilerHighlighter highlighter) {
return allHighlighterHighlights.get(highlighter);
}
void dispose() {
secondaryHighlighters.clear();
secondaryHighlightersByFunction.clear();
allHighlighterHighlights.clear();
}
}

View File

@ -120,7 +120,7 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
pointerDT = program.getDataTypeManager()
.addDataType(pointerDT, DataTypeConflictHandler.DEFAULT_HANDLER);
boolean isThisParam = DecompilerUtils.testForAutoParameterThis(var, function);
boolean isThisParam = DecompilerUtils.isThisParameter(var, function);
if (!isThisParam) {
commitVariable(var, pointerDT, isThisParam);
}

View File

@ -132,7 +132,7 @@ public class FillOutStructureHelper {
}
if (structDT == null) {
if (createClassIfNeeded && DecompilerUtils.testForAutoParameterThis(var, function)) {
if (createClassIfNeeded && DecompilerUtils.isThisParameter(var, function)) {
structDT = createUniqueClassNamespaceAndStructure(var, (int) size, function);
}
else {

View File

@ -1016,6 +1016,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
new RemoveAllSecondaryHighlightsAction();
setGroupInfo(removeAllSecondadryHighlightsAction, highlightGroup, subGroupPosition++);
PreviousHighlightedTokenAction previousHighlightedTokenAction =
new PreviousHighlightedTokenAction();
setGroupInfo(previousHighlightedTokenAction, highlightGroup, subGroupPosition++);
NextHighlightedTokenAction nextHighlightedTokenAction = new NextHighlightedTokenAction();
setGroupInfo(nextHighlightedTokenAction, highlightGroup, subGroupPosition++);
String convertGroup = "7 - Convert Group";
subGroupPosition = 0;
RemoveEquateAction removeEquateAction = new RemoveEquateAction();
@ -1122,6 +1129,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(setSecondaryHighlightColorChooserAction);
addLocalAction(removeSecondaryHighlightAction);
addLocalAction(removeAllSecondadryHighlightsAction);
addLocalAction(nextHighlightedTokenAction);
addLocalAction(previousHighlightedTokenAction);
addLocalAction(convertBinaryAction);
addLocalAction(convertDecAction);
addLocalAction(convertFloatAction);
@ -1165,7 +1174,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private void setGroupInfo(DockingAction action, String group, int subGroupPosition) {
MenuData popupMenuData = action.getPopupMenuData();
popupMenuData.setMenuGroup(group);
popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition));
// Some groups have numbers reach double-digits. These will not compare correctly unless
// padded. Ensure all string numbers are at least 2 digits.
String numberString = Integer.toString(subGroupPosition);
if (numberString.length() == 1) {
numberString = '0' + numberString;
}
popupMenuData.setMenuSubGroup(numberString);
}
private void graphServiceRemoved() {

View File

@ -61,7 +61,7 @@ public class DecompilerStructureVariableAction extends CreateStructureVariableAc
HighVariable var = tokenAtCursor.getHighVariable();
if (var != null && !(var instanceof HighConstant)) {
dt = var.getDataType();
isThisParam = DecompilerUtils.testForAutoParameterThis(var, function);
isThisParam = DecompilerUtils.isThisParameter(var, function);
}
if (dt == null || dt.getLength() > maxPointerSize) {

View File

@ -0,0 +1,99 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile.actions;
import java.util.List;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import docking.widgets.fieldpanel.field.Field;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.TokenIterator;
import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
/**
* An action to navigate to the next token highlighted by the user via the middle-mouse.
*/
public class NextHighlightedTokenAction extends AbstractDecompilerAction {
public NextHighlightedTokenAction() {
super("Next Highlihted Token");
setPopupMenuData(new MenuData(new String[] { "Next Highlight" }, "Decompile"));
setKeyBindingData(new KeyBindingData("Ctrl period"));
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight"));
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
if (!context.hasRealFunction()) {
return false;
}
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
if (highlights != null) {
return highlights.size() > 1;
}
return false;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
ClangToken cursorToken = context.getTokenAtCursor();
TokenIterator it = new TokenIterator(cursorToken, true);
it.next(); // ignore the current token
if (goToNexToken(panel, it, highlights)) {
return; // found another token in the current direction
}
// this means there are no more occurrences in the current direction; wrap the search
ClangToken firstToken = getFirstToken(panel);
it = new TokenIterator(firstToken, true);
goToNexToken(panel, it, highlights);
}
private ClangToken getFirstToken(DecompilerPanel panel) {
List<Field> fields = panel.getFields();
Field line = fields.get(0);
ClangTextField tf = (ClangTextField) line;
return tf.getFirstToken();
}
private boolean goToNexToken(DecompilerPanel panel, TokenIterator it,
TokenHighlights highlights) {
while (it.hasNext()) {
ClangToken nextToken = it.next();
HighlightToken hlToken = highlights.get(nextToken);
if (hlToken == null) {
continue;
}
ClangToken token = hlToken.getToken();
panel.goToToken(token);
return true;
}
return false;
}
}

View File

@ -0,0 +1,100 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile.actions;
import java.util.List;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import docking.widgets.fieldpanel.field.Field;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.TokenIterator;
import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
/**
* An action to navigate to the previous token highlighted by the user via the middle-mouse.
*/
public class PreviousHighlightedTokenAction extends AbstractDecompilerAction {
public PreviousHighlightedTokenAction() {
super("Previous Highlighted Token");
setPopupMenuData(new MenuData(new String[] { "Previous Highlight" }, "Decompile"));
setKeyBindingData(new KeyBindingData("Ctrl comma"));
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight"));
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
if (!context.hasRealFunction()) {
return false;
}
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
if (highlights != null) {
return highlights.size() > 1;
}
return false;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
ClangToken cursorToken = context.getTokenAtCursor();
TokenIterator it = new TokenIterator(cursorToken, false);
it.next(); // ignore the current token
if (goToNexToken(panel, it, highlights)) {
return; // found another token in the current direction
}
// this means there are no more occurrences in the current direction; wrap the search
ClangToken lastToken = getLastToken(panel);
it = new TokenIterator(lastToken, false);
goToNexToken(panel, it, highlights);
}
private ClangToken getLastToken(DecompilerPanel panel) {
List<Field> fields = panel.getFields();
int lastLine = fields.size();
Field line = fields.get(lastLine - 1);
ClangTextField tf = (ClangTextField) line;
return tf.getLastToken();
}
private boolean goToNexToken(DecompilerPanel panel, TokenIterator it,
TokenHighlights highlights) {
while (it.hasNext()) {
ClangToken nextToken = it.next();
HighlightToken hlToken = highlights.get(nextToken);
if (hlToken == null) {
continue;
}
ClangToken token = hlToken.getToken();
panel.goToToken(token);
return true;
}
return false;
}
}

View File

@ -70,4 +70,9 @@ public class SliceHighlightColorProvider implements ColorProvider {
}
return c;
}
@Override
public String toString() {
return "Slice Color Provider " + hlColor;
}
}

View File

@ -46,6 +46,7 @@ import ghidra.app.plugin.core.decompile.actions.*;
import ghidra.app.util.AddEditDialog;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.listing.CodeUnit;
import ghidra.util.Msg;
public class DecompilerClangTest extends AbstractDecompilerTest {
@ -383,29 +384,32 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
setDecompilerLocation(line, charPosition);
ClangToken token = getToken();
String text = token.getText();
assertEquals("_printf", text);
String printfText = token.getText();
assertEquals("_printf", printfText);
highlight();
highlight(); // "printf" is secondary highlighted
// 5:30 "a->name"
line = 5;
charPosition = 38;
setDecompilerLocation(line, charPosition);
ClangToken token2 = getToken();
String text2 = token2.getText();
assertEquals("name", text2);
String nameText = token2.getText();
assertEquals("name", nameText);
Color color2 = highlight();
Color color2 = highlight(); // "name" is secondary highlighted
// 5:2 "_printf"
line = 5;
charPosition = 2;
setDecompilerLocation(line, charPosition);
removeSecondaryHighlight();
assertNoFieldsSecondaryHighlighted(text);
assertAllFieldsHighlighted(text2, color2);
Msg.debug(this, "test - remove");
removeSecondaryHighlight(); // remove "printf" highlight
assertNoFieldsSecondaryHighlighted(printfText);
assertAllFieldsHighlighted(nameText, color2);
}
@Test
@ -812,7 +816,106 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
}
@Test
public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight() {
public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight() {
/*
The middle mouse is a secondary highlight so that it persists as the user clicks around.
Middle mousing on an already middle moused highlight should clear the highlight. Middle
mousing on a new token should clear the original highlight and highlight the new token.
Decomp of '_call_structure_A':
1|
2| void _call_structure_A(A *a)
3|
4| {
5| _printf("call_structure_A: %s\n",a->name);
6| _printf("call_structure_A: %s\n",(a->b).name);
7| _printf("call_structure_A: %s\n",(a->b).c.name);
8| _printf("call_structure_A: %s\n",(a->b).c.d.name);
9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name);
10| _call_structure_B(&a->b);
11| return;
12| }
*/
decompile("100000d60"); // '_call_structure_A'
// 5:2 "_printf"
int line = 5;
int charPosition = 2;
setDecompilerLocation(line, charPosition);
ClangToken token = getToken();
String tokenText = token.getText();
assertEquals("_printf", tokenText);
middleMouse();
assertCombinedHighlightColor(token);
middleMouse();
assertNoFieldsSecondaryHighlighted(tokenText);
}
@Test
public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight_NewToken() {
/*
The middle mouse is a secondary highlight so that it persists as the user clicks around.
Middle mousing on an already middle moused highlight should clear the highlight. Middle
mousing on a new token should clear the original highlight and highlight the new token.
Decomp of '_call_structure_A':
1|
2| void _call_structure_A(A *a)
3|
4| {
5| _printf("call_structure_A: %s\n",a->name);
6| _printf("call_structure_A: %s\n",(a->b).name);
7| _printf("call_structure_A: %s\n",(a->b).c.name);
8| _printf("call_structure_A: %s\n",(a->b).c.d.name);
9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name);
10| _call_structure_B(&a->b);
11| return;
12| }
*/
decompile("100000d60"); // '_call_structure_A'
// 5:2 "_printf"
int line = 5;
int charPosition = 2;
setDecompilerLocation(line, charPosition);
ClangToken token = getToken();
String tokenText = token.getText();
assertEquals("_printf", tokenText);
middleMouse();
assertCombinedHighlightColor(token);
// 5:30 "a->name"
line = 5;
charPosition = 38;
setDecompilerLocation(line, charPosition);
ClangToken token2 = getToken();
String text2 = token2.getText();
assertEquals("name", text2);
middleMouse();
assertNoFieldsSecondaryHighlighted(tokenText);
assertCombinedHighlightColor(token2);
}
@Test
public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight_ExistingHighlight() {
/*
@ -850,7 +953,9 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
assertCombinedHighlightColor(token);
middleMouse();
assertAllFieldsHighlighted(tokenText, color);
ClangToken cursorToken = getToken(provider);
Predicate<ClangToken> ignores = t -> t == cursorToken;
assertAllFieldsHighlighted(tokenText, color, ignores);
}
@Test