diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/CTokenHighlightMatcher.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/CTokenHighlightMatcher.java
new file mode 100644
index 0000000000..cbed57e63e
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/CTokenHighlightMatcher.java
@@ -0,0 +1,45 @@
+/* ###
+ * 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;
+
+import java.awt.Color;
+
+/**
+ * The interface that clients must define to create a {@link DecompilerHighlighter}
+ *
+ *
Every function decompiled will trigger this matcher to get called. The order of method
+ * calls is: {@link #start(ClangNode)}, repeated calls to {@link #getTokenHighlight(ClangToken)}
+ * and then {@link #end()}.
+ *
+ * @see DecompilerHighlightService
+ */
+public interface CTokenHighlightMatcher {
+ public default void start(ClangNode root) {
+ // stub; provided for clients that may wish to work from the root
+ }
+
+ public default void end() {
+ // stub; provided for clients that may wish to perform cleanup when highlighting is finished
+ }
+
+ /**
+ * The basic method clients must implement to determine if a token should be highlighted.
+ * Returning a non-null Color will trigger the given token to be highlighted.
+ * @param token the token
+ * @return the highlight color or null
+ */
+ public Color getTokenHighlight(ClangToken token);
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangToken.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangToken.java
index 954010ec0c..9321e225b6 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangToken.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangToken.java
@@ -251,7 +251,7 @@ public class ClangToken implements ClangNode {
/**
* Get the high-level variable associate with this
* token or null otherwise
- * @return HighVariable
+ * @return HighVariable
*/
public HighVariable getHighVariable() {
if (Parent() instanceof ClangVariableDecl) {
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerHighlightService.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerHighlightService.java
new file mode 100644
index 0000000000..e4d6bb1d0a
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerHighlightService.java
@@ -0,0 +1,54 @@
+/* ###
+ * 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;
+
+/**
+ * A service that allows clients to create highlights in the form of background colors for
+ * {@link ClangToken}s in the Decompiler UI.
+ *
+ *
Note: highlights apply to a full token and not strings of text. To highlight a token, you
+ * create an instance of the {@link CTokenHighlightMatcher} to pass to one of the
+ * {@link #createHighlighter(String, CTokenHighlightMatcher)} methods of this interface.
+ *
+ *
There is no limit to the number of highlighters that may be installed. If multiple
+ * highlights overlap, then their colors will be blended.
+ */
+public interface DecompilerHighlightService {
+
+ /**
+ * Creates a highlighter that will use the given matcher to create highlights as functions
+ * get decompiled.
+ * @param tm the matcher
+ * @return the new highlighter
+ */
+ public DecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm);
+
+ /**
+ * A version of {@link #createHighlighter(String, CTokenHighlightMatcher)} that allows clients
+ * to specify an ID. This ID will be used to ensure that any existing highlighters with that
+ * ID will be removed before creating a new highlighter.
+ *
+ *
This method is convenient for scripts, since a script cannot hold on to any created
+ * highlighters between repeated script executions. A good value for script writers to use
+ * is the name of their script class.
+ *
+ * @param id the ID
+ * @param tm the matcher
+ * @return the new highlighter
+ */
+ public DecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm);
+
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerHighlighter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerHighlighter.java
new file mode 100644
index 0000000000..6674af5bf5
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerHighlighter.java
@@ -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;
+
+/**
+ * The highlighter interface passed to clients of the {@link DecompilerHighlightService}.
+ *
+ *
The expected workflow for this class is: create the highlighter, clients
+ * will request highlights via {@link #applyHighlights()}, clients will clear highlights via
+ * {@link #clearHighlights()} and the highlighter may be removed via {@link #dispose()}.
+ */
+public interface DecompilerHighlighter {
+
+ /**
+ * Call this method when you wish to apply your highlights.
+ */
+ public void applyHighlights();
+
+ /**
+ * Call this method when you wish to remove your highlights.
+ */
+ public void clearHighlights();
+
+ /**
+ * Call this method to remove your highlighter from the Decompiler.
+ */
+ public void dispose();
+
+ /**
+ * Returns the ID used by this highlighter. This will either be generated by this API or
+ * supplied by the client.
+ * @return the ID
+ * @see DecompilerHighlightService#createHighlighter(String, CTokenHighlightMatcher)
+ */
+ public String getId();
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java
new file mode 100644
index 0000000000..d153587ba8
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java
@@ -0,0 +1,161 @@
+/* ###
+ * 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 java.util.function.Supplier;
+
+import ghidra.app.decompiler.*;
+
+/**
+ * The implementation of {@link DecompilerHighlighter}. This will get created by the
+ * Decompiler and then handed to clients that use the {@link DecompilerHighlightService}. This
+ * is also used internally for 'secondary highlights'.
+ *
+ *
This class may be {@link #clone() cloned} or {@link #copy(DecompilerPanel) copied} as
+ * needed when the user creates a snapshot. Highlight service highlighters will be cloned;
+ * secondary highlighters will be copied. Cloning allows this class to delegate highlighting
+ * and cleanup for clones. Contrastingly, copying allows the secondary highlights to operate
+ * independently.
+ */
+class ClangDecompilerHighlighter implements DecompilerHighlighter {
+
+ protected String id;
+ private DecompilerPanel decompilerPanel;
+ private CTokenHighlightMatcher matcher;
+ private Set clones = new HashSet<>();
+
+ ClangDecompilerHighlighter(DecompilerPanel panel, CTokenHighlightMatcher matcher) {
+ UUID uuId = UUID.randomUUID();
+ this.id = uuId.toString();
+ this.decompilerPanel = panel;
+ this.matcher = matcher;
+ }
+
+ ClangDecompilerHighlighter(String id, DecompilerPanel panel, CTokenHighlightMatcher matcher) {
+ this.id = id;
+ this.decompilerPanel = panel;
+ this.matcher = matcher;
+ }
+
+ /**
+ * Create a clone of this highlighter and tracks the clone
+ * @param panel the panel
+ * @return the highlighter
+ */
+ ClangDecompilerHighlighter clone(DecompilerPanel panel) {
+ // note: we re-use the ID to make tracking easier
+ ClangDecompilerHighlighter clone = new ClangDecompilerHighlighter(id, panel, matcher);
+ clones.add(clone);
+ return clone;
+ }
+
+ /**
+ * Creates a copy of this highlighter that is not tracked by this highlighter
+ * @param panel the panel
+ * @return the highlighter
+ */
+ ClangDecompilerHighlighter copy(DecompilerPanel panel) {
+ return new ClangDecompilerHighlighter(panel, matcher);
+ }
+
+ @Override
+ public void applyHighlights() {
+
+ if (decompilerPanel == null) {
+ return; // disposed
+ }
+
+ clearHighlights();
+
+ ClangLayoutController layoutModel =
+ (ClangLayoutController) decompilerPanel.getLayoutModel();
+ ClangTokenGroup root = layoutModel.getRoot();
+
+ Map highlights = new HashMap<>();
+ try {
+ matcher.start(root);
+ gatherHighlights(root, highlights);
+ }
+ finally {
+ matcher.end();
+ }
+
+ Supplier extends Collection> tokens = () -> highlights.keySet();
+ ColorProvider colorProvider = t -> highlights.get(t);
+ decompilerPanel.addHighlighterHighlights(this, tokens, colorProvider);
+
+ clones.forEach(c -> c.applyHighlights());
+ }
+
+ private void gatherHighlights(ClangTokenGroup root, Map results) {
+
+ int n = root.numChildren();
+ for (int i = 0; i < n; ++i) {
+ ClangNode child = root.Child(i);
+ getHighlight(child, results);
+
+ if (child instanceof ClangTokenGroup) {
+ gatherHighlights(((ClangTokenGroup) child), results);
+ }
+ }
+ }
+
+ private void getHighlight(ClangNode node, Map results) {
+ if (node instanceof ClangTokenGroup) {
+ return;
+ }
+
+ ClangToken token = (ClangToken) node;
+ Color color = matcher.getTokenHighlight(token);
+ if (color != null) {
+ results.put(token, color);
+ }
+ }
+
+ @Override
+ public void clearHighlights() {
+ if (decompilerPanel == null) {
+ return; // disposed
+ }
+
+ decompilerPanel.removeHighlighterHighlights(this);
+ clones.forEach(c -> c.clearHighlights());
+ }
+
+ @Override
+ public void dispose() {
+ if (decompilerPanel == null) {
+ return; // disposed
+ }
+
+ clearHighlights();
+ decompilerPanel.removeHighlighter(id);
+ decompilerPanel = null;
+ clones.forEach(c -> c.dispose());
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ' ' + id;
+ }
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java
index 6ffcafc5f2..825d35b496 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java
@@ -17,14 +17,16 @@ package ghidra.app.decompiler.component;
import java.awt.Color;
import java.util.*;
-import java.util.function.Function;
+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;
import ghidra.app.decompiler.*;
-import ghidra.app.plugin.core.decompile.actions.TokenHighlightColorProvider;
+import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.ColorUtils;
import util.CollectionUtils;
@@ -33,17 +35,30 @@ import util.CollectionUtils;
* Class to handle highlights for a decompiled function.
*
* This class does not painting directly. Rather, this class tracks the currently highlighted
- * tokens and then sets the highlight color on the token when it is highlighted and clears the
+ * tokens and then sets the highlight color on the token when it is highlighted and clears the
* highlight color when the highlight is removed.
*
- *
This class maintains the notion of 'primary' highlights and 'secondary' highlights.
- * Primary highlights are considered transient and get cleared whenever the location changes.
- * Secondary highlights will stay until they are manually cleared by a user action. Primary
- * highlights happen when the user clicks around the Decompiler. They show state such as the
- * current field, impact of a variable (via a slicing action), or related syntax (such as
- * matching braces). Secondary highlights are triggered by the user to show all occurrences of
- * a particular variable. Further, the user can apply multiple secondary highlights at the
- * same time, with different colors for each highlight.
+ *
This class maintains the following types of highlights:
+ *
+ * - Primary 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)
+ *
+ * - Secondary Highlights - triggered by the user to show all occurrences of a particular
+ * variable; they will stay until they are manually cleared by a user action. The user can \
+ * apply multiple secondary highlights at the same time, with different colors for each
+ * highlight.
+ * These highlights apply to the function in use when the highlight is created. Thus,
+ * each function has a unique set of highlights that is maintained between decompilation.
+ *
+ * - Global Highlights - triggered by clients of the {@link DecompilerHighlightService}; they
+ * will stay until the client of the service clears the highlight.
+ * These highlights apply to every function that is decompiler.
+ *
+ *
+ *
+ * When multiple highlights overlap, their colors will be blended.
*/
public abstract class ClangHighlightController {
@@ -56,14 +71,23 @@ public abstract class ClangHighlightController {
return c;
}
- // Note: Most of the methods in this class were extracted from the ClangLayoutController class
- // and the DecompilerPanel class.
-
protected Color defaultHighlightColor = DEFAULT_HIGHLIGHT_COLOR;
protected Color defaultParenColor = DEFAULT_HIGHLIGHT_COLOR;
private TokenHighlights primaryHighlightTokens = new TokenHighlights();
- private TokenHighlights secondaryHighlightTokens = new TokenHighlights();
+
+ private Map> 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 secondaryHighlighters = new HashSet<>();
+
+ // all highlighters, including secondary and highlight service highlighters
+ private Map highlighterHighlights =
+ new HashMap<>();
+
+ // color supplier for secondary highlights
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
/**
@@ -80,7 +104,22 @@ public abstract class ClangHighlightController {
defaultHighlightColor = c;
}
- public String getHighlightedText() {
+ /**
+ * Returns the color provider used by this class to generate colors. The initial color
+ * selection is random. Repeated calls to get a color for the same token will return the same
+ * 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();
@@ -88,20 +127,78 @@ public abstract class ClangHighlightController {
return null;
}
+ /**
+ * An value that is updated every time a new highlight is added. This allows clients to
+ * determine if a buffered update request is still valid.
+ * @return the value
+ */
public long getUpdateId() {
return updateId;
}
+ public boolean hasPrimaryHighlight(ClangToken token) {
+ return primaryHighlightTokens.contains(token);
+ }
+
+ public boolean hasSecondaryHighlight(ClangToken token) {
+ return getSecondaryHighlight(token) != null;
+ }
+
+ public boolean hasSecondaryHighlights() {
+ return !secondaryHighlighters.isEmpty();
+ }
+
+ 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;
+ }
+
public TokenHighlightColors getSecondaryHighlightColors() {
return secondaryHighlightColors;
}
- public TokenHighlights getPrimaryHighlightedTokens() {
+ public TokenHighlights getPrimaryHighlights() {
return primaryHighlightTokens;
}
- public TokenHighlights getSecondaryHighlightedTokens() {
- return secondaryHighlightTokens;
+ /**
+ * Returns all secondary highlighters for the given function. This allows clients to update
+ * the secondary highlight state of a given function without affecting highlights applied to
+ * other functions.
+ * @param function the function
+ * @return the highlighters
+ */
+ public Set getSecondaryHighlightersByFunction(Function function) {
+ return new HashSet<>(secondaryHighlightersbyFunction.get(function));
+ }
+
+ /**
+ * Returns all global highlighters installed in this controller. The global highlighters apply
+ * to all functions. This is in contrast to secondary highlighters, which are
+ * function-specific.
+ * @return the highlighters
+ */
+ public Set getGlobalHighlighters() {
+ Set allHighlighters = highlighterHighlights.keySet();
+ Set results = new HashSet<>(allHighlighters);
+ results.removeAll(secondaryHighlighters);
+ return results;
+ }
+
+ /**
+ * Gets all highlights for the given highlighter.
+ * @param highlighter the highlighter
+ * @return the highlights
+ * @see #getPrimaryHighlights()
+ */
+ public TokenHighlights getHighlighterHighlights(DecompilerHighlighter highlighter) {
+ return highlighterHighlights.get(highlighter);
}
/**
@@ -131,28 +228,36 @@ public abstract class ClangHighlightController {
}
public void clearPrimaryHighlights() {
- doClearHighlights(primaryHighlightTokens);
+ Consumer clearAll = token -> {
+ token.setMatchingToken(false);
+ updateHighlightColor(token);
+ };
+
+ doClearHighlights(primaryHighlightTokens, clearAll);
notifyListeners();
}
- public void clearAllHighlights() {
- doClearHighlights(primaryHighlightTokens);
- doClearHighlights(secondaryHighlightTokens);
- notifyListeners();
- }
-
- private void doClearHighlights(TokenHighlights tokenHighlights) {
+ private void doClearHighlights(TokenHighlights tokenHighlights, Consumer clearer) {
Iterator it = tokenHighlights.iterator();
while (it.hasNext()) {
HighlightToken highlight = it.next();
+
+ // must remove the highlight before calling the clearer as that may call back into the
+ // TokenHighlights we are clearing
it.remove();
ClangToken token = highlight.getToken();
- token.setMatchingToken(false);
- updateHighlightColor(token);
+ clearer.accept(token);
}
- tokenHighlights.clear();
}
+ /**
+ * Toggles the primary highlight state of the given set of tokens. If the given tokens do not
+ * all have the same highlight state (highlights on or off), then the highlights will be
+ * cleared. If all tokens are not highlighted, then they will all become highlighted.
+ *
+ * @param hlColor the highlight color
+ * @param tokens the tokens
+ */
public void togglePrimaryHighlights(Color hlColor, Supplier> tokens) {
boolean isAllHighlighted = true;
@@ -175,67 +280,105 @@ public abstract class ClangHighlightController {
addPrimaryHighlights(tokens, hlColor);
}
- public boolean hasPrimaryHighlight(ClangToken token) {
- return primaryHighlightTokens.contains(token);
- }
-
- public boolean hasSecondaryHighlight(ClangToken token) {
- return secondaryHighlightTokens.contains(token);
- }
-
- public Set getSecondaryHighlightsByFunction(
- ghidra.program.model.listing.Function f) {
- Set highlights = secondaryHighlightTokens.getHighlightsByFunction(f);
- return highlights;
- }
-
- public void removeSecondaryHighlights(ghidra.program.model.listing.Function f) {
- Set oldHighlights = secondaryHighlightTokens.removeHighlightsByFunction(f);
- for (HighlightToken hl : oldHighlights) {
- ClangToken token = hl.getToken();
- updateHighlightColor(token);
+ /**
+ * Removes all secondary highlights for the given function
+ * @param f the function
+ */
+ public void removeSecondaryHighlights(Function f) {
+ List highlighters = secondaryHighlightersbyFunction.get(f);
+ for (ClangDecompilerHighlighter highlighter : highlighters) {
+ TokenHighlights highlights = highlighterHighlights.get(highlighter);
+ Consumer clearHighlight = token -> updateHighlightColor(token);
+ doClearHighlights(highlights, clearHighlight);
}
notifyListeners();
}
+ /**
+ * Removes all secondary highlights for the given token
+ * @param token the token
+ * @see #removeSecondaryHighlights(Function)
+ */
public void removeSecondaryHighlights(ClangToken token) {
- secondaryHighlightTokens.remove(token);
- }
-
- public void removeSecondaryHighlights(Supplier extends Collection> tokens) {
- for (ClangToken clangToken : tokens.get()) {
- secondaryHighlightTokens.remove(clangToken);
- updateHighlightColor(clangToken);
+ DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
+ if (highlighter != null) {
+ highlighter.dispose(); // this will call removeHighlighterHighlights()
}
notifyListeners();
}
- public void addSecondaryHighlights(String tokenText,
- Supplier extends Collection> tokens) {
- Color highlightColor = secondaryHighlightColors.getColor(tokenText);
- addSecondaryHighlights(tokens, highlightColor);
+ 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 addSecondaryHighlights(Supplier extends Collection> tokens,
- Color hlColor) {
- Function colorProvider = token -> hlColor;
- addTokensToHighlights(tokens.get(), colorProvider, secondaryHighlightTokens);
+ public void removeHighlighter(DecompilerHighlighter highlighter) {
+ removeHighlighterHighlights(highlighter);
+ secondaryHighlighters.remove(highlighter);
+ highlighterHighlights.remove(highlighter);
}
- public void addPrimaryHighlights(Supplier extends Collection> tokens,
+ /**
+ * Removes all highlights for this highlighter across all functions
+ * @param highlighter the highlighter
+ */
+ public void removeHighlighterHighlights(DecompilerHighlighter highlighter) {
+ TokenHighlights highlighterTokens = highlighterHighlights.get(highlighter);
+ if (highlighterTokens == null) {
+ return;
+ }
+
+ Consumer clearHighlight = token -> updateHighlightColor(token);
+ doClearHighlights(highlighterTokens, clearHighlight);
+ notifyListeners();
+ }
+
+ /**
+ * Adds the given secondary highlighter, but does not create any highlights. All secondary
+ * highlighters pertain to a given function.
+ * @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());
+ }
+
+ /**
+ * Adds the given highlighter, but does not create any highlights
+ * @param highlighter the highlighter
+ */
+ public void addHighlighter(ClangDecompilerHighlighter highlighter) {
+ highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
+ }
+
+ public void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
+ Supplier extends Collection> tokens,
+ ColorProvider colorProvider) {
+
+ Objects.requireNonNull(highlighter);
+ TokenHighlights highlighterTokens =
+ highlighterHighlights.computeIfAbsent(highlighter, k -> new TokenHighlights());
+ addTokensToHighlights(tokens.get(), colorProvider, highlighterTokens);
+ }
+
+ private void addPrimaryHighlights(Supplier extends Collection> tokens,
Color hlColor) {
- Function colorProvider = token -> hlColor;
+ ColorProvider colorProvider = token -> hlColor;
addTokensToHighlights(tokens.get(), colorProvider, primaryHighlightTokens);
}
- public void addPrimaryHighlights(ClangNode parentNode,
- TokenHighlightColorProvider colorProvider) {
-
- Set tokens = new HashSet<>();
- gatherAllTokens(parentNode, tokens);
- addTokensToHighlights(tokens, colorProvider::getColor, primaryHighlightTokens);
- }
-
public void addPrimaryHighlights(ClangNode parentNode, Set ops, Color hlColor) {
addPrimaryHighlights(parentNode, token -> {
@@ -244,18 +387,25 @@ public abstract class ClangHighlightController {
});
}
+ public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
+
+ Set tokens = new HashSet<>();
+ gatherAllTokens(parentNode, tokens);
+ addTokensToHighlights(tokens, colorProvider::getColor, primaryHighlightTokens);
+ }
+
private void addPrimaryHighlights(Collection tokens, Color hlColor) {
- Function colorProvider = token -> hlColor;
+ ColorProvider colorProvider = token -> hlColor;
addTokensToHighlights(tokens, colorProvider, primaryHighlightTokens);
}
private void addTokensToHighlights(Collection tokens,
- Function colorProvider, TokenHighlights currentHighlights) {
+ ColorProvider colorProvider, TokenHighlights currentHighlights) {
updateId++;
for (ClangToken clangToken : tokens) {
- Color color = colorProvider.apply(clangToken);
+ Color color = colorProvider.getColor(clangToken);
doAddHighlight(clangToken, color, currentHighlights);
}
notifyListeners();
@@ -283,25 +433,79 @@ public abstract class ClangHighlightController {
t.setHighlight(combinedColor);
}
+ private void add(List colors, HighlightToken hlToken) {
+ if (hlToken != null) {
+ colors.add(hlToken.getColor());
+ }
+ }
+
+ private void add(List colors, Color c) {
+ if (c != null) {
+ colors.add(c);
+ }
+ }
+
+ /**
+ * Returns the current highlight color for the given token, based upon all known highlights,
+ * primary, secondary and highlighters
+ * @param t the token
+ * @return the color
+ */
public Color getCombinedColor(ClangToken t) {
+ // 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 secondaryHl = secondaryHighlightTokens.get(t);
- Color primary = primaryHl == null ? null : primaryHl.getColor();
- Color secondary = secondaryHl == null ? null : secondaryHl.getColor();
+ Color blendedHlColor = blendHighlighterColors(t);
- if (primary == null) {
- if (secondary == null) {
- return null;
+ List allColors = new ArrayList<>();
+ add(allColors, primaryHl);
+ add(allColors, blendedHlColor);
+
+ Color blended = blend(allColors);
+ return blended;
+ }
+
+ public Color blend(List colors) {
+
+ if (colors.isEmpty()) {
+ return null;
+ }
+
+ if (colors.size() == 1) {
+ return CollectionUtils.any(colors);
+ }
+
+ Color lastColor = colors.get(0);
+ for (int i = 1; i < colors.size(); i++) {
+ Color nextColor = colors.get(i);
+ lastColor = ColorUtils.blend(lastColor, nextColor, .8f);
+ }
+
+ return lastColor;
+ }
+
+ private Color blendHighlighterColors(ClangToken token) {
+
+ Color lastColor = null;
+ Collection allHighlights = highlighterHighlights.values();
+ for (TokenHighlights highlights : allHighlights) {
+ HighlightToken hlToken = highlights.get(token);
+ if (hlToken == null) {
+ continue;
+ }
+
+ Color nextColor = hlToken.getColor();
+ if (lastColor != null) {
+ lastColor = ColorUtils.blend(lastColor, nextColor, .8f);
+ }
+ else {
+ lastColor = nextColor;
}
- return secondary;
}
- if (secondary == null) {
- return primary;
- }
-
- return ColorUtils.blend(primary, secondary, .8f);
+ return lastColor;
}
/**
@@ -372,7 +576,7 @@ public abstract class ClangHighlightController {
return results;
}
- public void addHighlightBrace(ClangSyntaxToken token, Color highlightColor) {
+ public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
if (DecompilerUtils.isBrace(token)) {
highlightBrace(token, highlightColor);
@@ -402,4 +606,12 @@ public abstract class ClangHighlightController {
listener.tokenHighlightsChanged();
}
}
+
+ public void dispose() {
+ listeners.clear();
+ primaryHighlightTokens.clear();
+ secondaryHighlighters.clear();
+ secondaryHighlightersbyFunction.clear();
+ highlighterHighlights.clear();
+ }
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/TokenHighlightColorProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ColorProvider.java
similarity index 67%
rename from Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/TokenHighlightColorProvider.java
rename to Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ColorProvider.java
index 801f101263..b7ece05ae7 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/TokenHighlightColorProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ColorProvider.java
@@ -13,16 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package ghidra.app.plugin.core.decompile.actions;
+package ghidra.app.decompiler.component;
import java.awt.Color;
+import java.util.function.Function;
import ghidra.app.decompiler.ClangToken;
/**
- * Provides highlight color for the given token
+ * Functional interface to allow us to map a token to a color.
+ *
+ * This class allows us to avoid the namespace conflicts of Java's Function and Ghidra's
+ * Function since we can declare a {@code ColorProvider} as a parameter to methods instead of
+ * a {@link Function}.
*/
-public interface TokenHighlightColorProvider {
+public interface ColorProvider {
/**
* Returns a color for the given token
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java
index ef2b18edb8..719e625875 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java
@@ -65,6 +65,7 @@ public class DecompilerController {
//==================================================================================================
// Methods call by the provider
//==================================================================================================
+
/**
* Called by the provider when the provider is disposed. Once dispose is called, it should
* never be used again.
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
index 9dffe4f771..f4073f9e58 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
@@ -20,9 +20,7 @@ import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.util.*;
import java.util.List;
-import java.util.Map.Entry;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
import javax.swing.JComponent;
import javax.swing.JPanel;
@@ -43,7 +41,6 @@ import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider;
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
-import ghidra.app.plugin.core.decompile.actions.TokenHighlightColorProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
@@ -53,7 +50,6 @@ import ghidra.program.util.ProgramSelection;
import ghidra.util.*;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.task.SwingUpdateManager;
-import util.CollectionUtils;
/**
* Class to handle the display of a decompiled function
@@ -74,6 +70,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private HighlightFactory hlFactory;
private ClangHighlightController highlightController;
+ private Map highlightersById = new HashMap<>();
private PendingHighlightUpdate pendingHighlightUpdate;
private SwingUpdateManager highlighCursorUpdater = new SwingUpdateManager(() -> {
if (pendingHighlightUpdate != null) {
@@ -144,58 +141,66 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return fieldPanel;
}
- public void applySecondaryHighlights(Map highlightsByName) {
-
- Set> entries = highlightsByName.entrySet();
- for (Entry entry : entries) {
- String tokenName = entry.getKey();
- Color color = entry.getValue();
- Supplier> lazyTokens = () -> findTokensByName(tokenName);
- highlightController.addSecondaryHighlights(lazyTokens, color);
- }
- }
+//==================================================================================================
+// Highlight Methods
+//==================================================================================================
public TokenHighlightColors getSecondaryHighlightColors() {
return highlightController.getSecondaryHighlightColors();
}
- public TokenHighlights getSecondaryHighlightedTokens() {
- return highlightController.getSecondaryHighlightedTokens();
+ public boolean hasSecondaryHighlights() {
+ return highlightController.hasSecondaryHighlights();
}
+ public boolean hasSecondaryHighlight(ClangToken token) {
+ return highlightController.hasSecondaryHighlight(token);
+ }
+
+ public Color getSecondaryHighlight(ClangToken token) {
+ return highlightController.getSecondaryHighlight(token);
+ }
+
+ public TokenHighlights getHighlights(DecompilerHighlighter highligter) {
+ return highlightController.getHighlighterHighlights(highligter);
+ }
+
+ private Set getSecondaryHighlihgtersByFunction(Function function) {
+ return highlightController.getSecondaryHighlightersByFunction(function);
+ }
+
+ /**
+ * Removes all secondary highlights for the current function
+ */
public void removeSecondaryHighlights() {
Function function = controller.getFunction();
highlightController.removeSecondaryHighlights(function);
}
public void removeSecondaryHighlight(ClangToken token) {
- removeSecondaryHighlight(token.getText());
- }
-
- private void removeSecondaryHighlight(String tokenText) {
- Supplier> lazyTokens = () -> findTokensByName(tokenText);
- highlightController.removeSecondaryHighlights(lazyTokens);
+ highlightController.removeSecondaryHighlights(token);
}
public void addSecondaryHighlight(ClangToken token) {
- String tokenText = token.getText();
- addSecondaryHighlight(tokenText);
- }
-
- private void addSecondaryHighlight(String tokenText) {
- Supplier> lazyTokens = () -> {
- return findTokensByName(tokenText);
- };
- highlightController.addSecondaryHighlights(tokenText, lazyTokens);
+ ColorProvider cp = highlightController.getRandomColorProvider();
+ addSecondaryHighlight(token.getText(), cp);
}
public void addSecondaryHighlight(ClangToken token, Color color) {
- addSecondaryHighlight(token.getText(), color);
+ ColorProvider cp = t -> color;
+ addSecondaryHighlight(token.getText(), cp);
}
- private void addSecondaryHighlight(String tokenText, Color color) {
- Supplier> lazyTokens = () -> findTokensByName(tokenText);
- highlightController.addSecondaryHighlights(lazyTokens, color);
+ private void addSecondaryHighlight(String tokenText, ColorProvider colorProvider) {
+ NameTokenMatcher matcher = new NameTokenMatcher(tokenText, colorProvider);
+ ClangDecompilerHighlighter highlighter = createHighlighter(matcher);
+ applySecondaryHighlights(highlighter);
+ }
+
+ private void applySecondaryHighlights(ClangDecompilerHighlighter highlighter) {
+ Function function = decompileData.getFunction();
+ highlightController.addSecondaryHighlighter(function, highlighter);
+ highlighter.applyHighlights();
}
private void togglePrimaryHighlight(FieldLocation location, Field field, Color highlightColor) {
@@ -204,6 +209,156 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
highlightController.togglePrimaryHighlights(middleMouseHighlightColor, lazyTokens);
}
+ void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
+ Supplier extends Collection> tokens, ColorProvider colorProvider) {
+ highlightController.addHighlighterHighlights(highlighter, tokens, colorProvider);
+ }
+
+ void removeHighlighterHighlights(DecompilerHighlighter highlighter) {
+ highlightController.removeHighlighterHighlights(highlighter);
+ }
+
+ public ClangDecompilerHighlighter 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);
+ if (currentHighlighter != null) {
+ currentHighlighter.dispose();
+ }
+
+ ClangDecompilerHighlighter newHighlighter = new ClangDecompilerHighlighter(id, this, tm);
+ highlightersById.put(id, newHighlighter);
+ highlightController.addHighlighter(newHighlighter);
+ return newHighlighter;
+ }
+
+ public DecompilerHighlighter getHighlighter(String id) {
+ return highlightersById.get(id);
+ }
+
+ void removeHighlighter(String id) {
+ ClangDecompilerHighlighter highlighter = highlightersById.remove(id);
+ highlightController.removeHighlighter(highlighter);
+ }
+
+ public void clearPrimaryHighlights() {
+ highlightController.clearPrimaryHighlights();
+ }
+
+ public void addHighlights(Set varnodes, ColorProvider colorProvider) {
+ ClangTokenGroup root = layoutMgr.getRoot();
+ highlightController.addPrimaryHighlights(root, colorProvider);
+ }
+
+ public void addHighlights(Set ops, Color hlColor) {
+ ClangTokenGroup root = layoutMgr.getRoot();
+ highlightController.addPrimaryHighlights(root, ops, hlColor);
+ }
+
+ public String getHighlightedText() {
+ return highlightController.getPrimaryHighlightedText();
+ }
+
+ public void setHighlightController(ClangHighlightController highlightController) {
+ if (this.highlightController != null) {
+ this.highlightController.removeListener(this);
+ }
+
+ this.highlightController = ClangHighlightController.dummyIfNull(highlightController);
+ highlightController.setHighlightColor(currentVariableHighlightColor);
+ highlightController.addListener(this);
+ }
+
+ public ClangHighlightController getHighlightController() {
+ return highlightController;
+ }
+
+ @Override
+ public void tokenHighlightsChanged() {
+ repaint();
+ }
+
+ /**
+ * This is function is used to alert the panel that a token was renamed.
+ * If the token that is being renamed had a secondary highlight, we must re-apply the highlight
+ * to the new token.
+ *
+ * @param token the token being renamed
+ * @param newName the new name of the token
+ */
+ public void tokenRenamed(ClangToken token, String newName) {
+
+ Color hlColor = highlightController.getSecondaryHighlight(token);
+ if (hlColor == null) {
+ return; // not highlighted
+ }
+
+ // remove the old highlighter
+ highlightController.removeSecondaryHighlights(token);
+
+ controller.doWhenNotBusy(() -> {
+ addSecondaryHighlight(newName, t -> hlColor);
+ });
+ }
+
+ private void cloneGlobalHighlighters(DecompilerPanel sourcePanel) {
+
+ Set allHighlighters =
+ sourcePanel.highlightController.getGlobalHighlighters();
+ for (ClangDecompilerHighlighter otherHighlighter : allHighlighters) {
+
+ ClangDecompilerHighlighter newHighlighter = otherHighlighter.clone(this);
+ highlightersById.put(newHighlighter.getId(), newHighlighter);
+
+ TokenHighlights otherHighlighterTokens =
+ sourcePanel.highlightController.getHighlighterHighlights(otherHighlighter);
+ if (otherHighlighterTokens == null || otherHighlighterTokens.isEmpty()) {
+ // The highlighter has been created but no highlights have been applied. It is up
+ // to the client to apply the highlights. The new highlighter will respond to the
+ // client request if the later apply the highlights.
+ continue;
+ }
+
+ newHighlighter.applyHighlights();
+ }
+ }
+
+ /**
+ * Called by the provider to clone all highlights in the source panel and apply them to this
+ * panel
+ * @param sourcePanel the panel that was cloned
+ */
+ public void cloneHighlights(DecompilerPanel sourcePanel) {
+
+ cloneGlobalHighlighters(sourcePanel);
+
+ //
+ // Keep only those secondary highlighters for the current function. This ensures that the
+ // clone will match the cloned decompiler.
+ //
+ Function function = decompileData.getFunction();
+ Set 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);
+ highlightersById.put(newHighlighter.getId(), newHighlighter);
+ applySecondaryHighlights(newHighlighter);
+ }
+ }
+
+//==================================================================================================
+// End Highlight Methods
+//==================================================================================================
+
@Override
public void setBackground(Color bg) {
originalBackgroundColor = bg;
@@ -256,6 +411,21 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
currentSearchLocation = null;
reapplySecondaryHighlights();
+ reapplyHighlighterHighlights();
+ }
+
+ private void reapplyHighlighterHighlights() {
+
+ Function function = decompileData.getFunction();
+ if (function == null) {
+ return;
+ }
+
+ Collection values = highlightersById.values();
+ for (ClangDecompilerHighlighter highlighter : values) {
+ highlighter.clearHighlights();
+ highlighter.applyHighlights();
+ }
}
private void reapplySecondaryHighlights() {
@@ -265,26 +435,11 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
- // The existing highlights are based on the previously generated tokens, which no longer
- // exist. Use those tokens to highlight the current tokens, which are conceptually the
- // same tokens.
- Set oldHighlights =
- highlightController.getSecondaryHighlightsByFunction(function);
-
- //@formatter:off
- Map> tokensByName =
- CollectionUtils.asStream(oldHighlights)
- .map(ht -> ht.getToken())
- .collect(Collectors.groupingBy(t -> t.getText()))
- ;
- //@formatter:on
-
- Set>> entries = tokensByName.entrySet();
- for (Entry> entry : entries) {
- String name = entry.getKey();
- List oldTokens = entry.getValue();
- highlightController.removeSecondaryHighlights(() -> oldTokens);
- addSecondaryHighlight(name);
+ Set secondaryHighlighters =
+ getSecondaryHighlihgtersByFunction(function);
+ for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
+ highlighter.clearHighlights();
+ highlighter.applyHighlights();
}
}
@@ -332,9 +487,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
// to go to an actual token, since multiple tokens can share an address, we woudln't know
// which token is best.)
//
- // Note: at the time of this writing, not all fields have an address value. For
+ // Note: at the time of this writing, not all fields have an address value. For
// example, the ClangFuncNameToken, does not have an address. (It seems that most
- // of the tokens in the function signature do not have an address, which can
+ // of the tokens in the function signature do not have an address, which can
// probably be fixed.) So, to deal with this oddity, we will have some special
// case code below.
//
@@ -389,7 +544,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
/**
* Put cursor on first token in the list
- * @param tokens the tokens to search for
+ * @param tokens the tokens to search for
*/
private void goToBeginningOfLine(List tokens) {
if (tokens.isEmpty()) {
@@ -450,8 +605,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
/**
- * Translate Ghidra address to decompiler address. Functions within an overlay space are
- * decompiled in their physical space, therefore decompiler results refer to the
+ * Translate Ghidra address to decompiler address. Functions within an overlay space are
+ * decompiled in their physical space, therefore decompiler results refer to the
* functions underlying .physical space
*
* @param addr the Ghidra address
@@ -470,7 +625,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
/**
- * Translate Ghidra address set to decompiler address set. Functions within an overlay
+ * Translate Ghidra address set to decompiler address set. Functions within an overlay
* space are decompiled in their physical space, therefore decompiler results
* refer to the functions underlying .physical space
*
@@ -533,7 +688,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
layoutMgr = null;
decompilerHoverProvider.dispose();
highlighCursorUpdater.dispose();
- highlightController.clearAllHighlights();
+ highlightController.dispose();
+ highlightersById.clear();
}
private FontMetrics getFontMetrics(DecompileOptions decompileOptions) {
@@ -542,7 +698,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
/**
- * Passing false signals to disallow navigating to new functions from within the panel by
+ * Passing false signals to disallow navigating to new functions from within the panel by
* using the mouse.
* @param enabled false disabled mouse function navigation
*/
@@ -626,17 +782,17 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
// 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);
- }
- }
+// 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) {
@@ -855,10 +1011,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return SPECIAL_COLOR_DEF;
}
- public String getHighlightedText() {
- return highlightController.getHighlightedText();
- }
-
public FieldLocation getCursorPosition() {
return fieldPanel.getCursorLocation();
}
@@ -933,22 +1085,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return decompilerHoverProvider.isShowing();
}
- public void clearPrimaryHighlights() {
- highlightController.clearPrimaryHighlights();
- }
-
- public void addVarnodeHighlights(Set varnodes,
- TokenHighlightColorProvider colorProvider) {
-
- ClangTokenGroup root = layoutMgr.getRoot();
- highlightController.addPrimaryHighlights(root, colorProvider);
- }
-
- public void addPcodeOpHighlights(Set ops, Color hlColor) {
- ClangTokenGroup root = layoutMgr.getRoot();
- highlightController.addPrimaryHighlights(root, ops, hlColor);
- }
-
public List findTokensByName(String name) {
List tokens = new ArrayList<>();
doFindTokensByName(tokens, layoutMgr.getRoot(), name);
@@ -957,7 +1093,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private void doFindTokensByName(List tokens, ClangTokenGroup group, String name) {
- // TODO is it possible that two or more different variable tokens share the same name?
for (int i = 0; i < group.numChildren(); ++i) {
ClangNode child = group.Child(i);
if (child instanceof ClangTokenGroup) {
@@ -1006,54 +1141,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
highlightController.setHighlightColor(currentVariableHighlightColor);
}
- public void setHighlightController(ClangHighlightController highlightController) {
- if (this.highlightController != null) {
- this.highlightController.removeListener(this);
- }
-
- this.highlightController = ClangHighlightController.dummyIfNull(highlightController);
- highlightController.setHighlightColor(currentVariableHighlightColor);
- highlightController.addListener(this);
- }
-
- @Override
- public void tokenHighlightsChanged() {
- repaint();
- }
-
- /**
- * This is function is used to alert the panel that a token was renamed.
- * If the token that is being renamed had a secondary highlight, we must re-apply the highlight
- * to the new token.
- *
- * @param token the token being renamed
- * @param newName the new name of the token
- */
- public void tokenRenamed(ClangToken token, String newName) {
-
- if (!highlightController.hasSecondaryHighlight(token)) {
- return;
- }
-
- TokenHighlightColors colors = highlightController.getSecondaryHighlightColors();
- String oldName = token.getText();
- Color hlColor = colors.getColor(oldName);
- highlightController.removeSecondaryHighlights(token);
-
- controller.doWhenNotBusy(() -> {
-
- Supplier> lazyTokens = () -> findTokensByName(newName);
- highlightController.addSecondaryHighlights(lazyTokens, hlColor);
- });
- }
-
- public ClangHighlightController getHighlightController() {
- return highlightController;
- }
-
//==================================================================================================
// Inner Classes
-//==================================================================================================
+//==================================================================================================
private class SearchHighlightFactory implements HighlightFactory {
@@ -1138,10 +1228,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
/**
* Moves this field panel to the given line and column. Further, this navigation will
* fire an event to the rest of the tool. (This is in contrast to a field panel
- * goTo
, which we use to simply move the cursor, but not trigger an
- * tool-level navigation event.)
+ * goTo
, which we use to simply move the cursor, but not trigger an
+ * tool-level navigation event.)
*
- * @param lineNumber the line number
+ * @param lineNumber the line number
* @param column the column within the line
*/
void navigateTo(int lineNumber, int column) {
@@ -1171,7 +1261,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
void doUpdate() {
// Note: don't send this buffered cursor change highlight if some other highlight
- // has been applied. Otherwise, this highlight would overwrite the last
+ // has been applied. Otherwise, this highlight would overwrite the last
// applied highlight.
long lastUpdateId = highlightController.getUpdateId();
if (updateId == lastUpdateId) {
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java
index 35a4a88d2d..c4d14cfe6e 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java
@@ -43,7 +43,7 @@ public class LocationClangHighlightController extends ClangHighlightController {
addPrimaryHighlight(tok, defaultHighlightColor);
if (tok instanceof ClangSyntaxToken) {
addPrimaryHighlightToTokensForParenthesis((ClangSyntaxToken) tok, defaultParenColor);
- addHighlightBrace((ClangSyntaxToken) tok, defaultParenColor);
+ addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
}
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java
new file mode 100644
index 0000000000..934d3a1391
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java
@@ -0,0 +1,43 @@
+/* ###
+ * 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.CTokenHighlightMatcher;
+import ghidra.app.decompiler.ClangToken;
+
+/**
+ * Matcher used for secondary highlights in the Decompiler.
+ */
+class NameTokenMatcher implements CTokenHighlightMatcher {
+
+ private ColorProvider colorProvider;
+ private String name;
+
+ NameTokenMatcher(String name, ColorProvider colorProvider) {
+ this.name = name;
+ this.colorProvider = colorProvider;
+ }
+
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (name.equals(token.getText())) {
+ return colorProvider.getColor(token);
+ }
+ return null;
+ }
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java
index af6952e965..2a0b14677c 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java
@@ -16,15 +16,13 @@
package ghidra.app.decompiler.component;
import java.awt.Color;
-import java.util.Collection;
import java.util.Set;
-import java.util.function.Supplier;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
-import ghidra.app.decompiler.*;
-import ghidra.app.plugin.core.decompile.actions.TokenHighlightColorProvider;
+import ghidra.app.decompiler.ClangNode;
+import ghidra.app.decompiler.ClangSyntaxToken;
import ghidra.program.model.pcode.PcodeOp;
/**
@@ -38,13 +36,12 @@ public class NullClangHighlightController extends ClangHighlightController {
}
@Override
- public String getHighlightedText() {
+ public String getPrimaryHighlightedText() {
return null;
}
@Override
- public void addPrimaryHighlights(ClangNode parentNode,
- TokenHighlightColorProvider colorProvider) {
+ public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
// stub
}
@@ -54,18 +51,7 @@ public class NullClangHighlightController extends ClangHighlightController {
}
@Override
- public void addPrimaryHighlights(Supplier extends Collection> tokens,
- Color highlightColor) {
- // stub
- }
-
- @Override
- public void clearAllHighlights() {
- // stub
- }
-
- @Override
- public void addHighlightBrace(ClangSyntaxToken token, Color highlightColor) {
+ public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
// stub
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlights.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlights.java
index 4115ffb02a..2fa011d2bd 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlights.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlights.java
@@ -17,15 +17,12 @@ package ghidra.app.decompiler.component;
import java.awt.Color;
import java.util.*;
-import java.util.Map.Entry;
-import ghidra.app.decompiler.*;
-import ghidra.program.model.listing.Function;
-import ghidra.program.model.pcode.HighFunction;
+import ghidra.app.decompiler.ClangToken;
/**
* A simple class to manage {@link HighlightToken}s used to create highlights in the Decompiler.
- * This class allows clients to access highlights either by a {@link ClangToken} or a
+ * This class allows clients to access highlights either by a {@link ClangToken} or a
* {@link HighlightToken}.
*/
public class TokenHighlights implements Iterable {
@@ -52,19 +49,6 @@ public class TokenHighlights implements Iterable {
return new TokenKey(t);
}
- private Function getFunction(ClangToken t) {
- ClangFunction cFunction = t.getClangFunction();
- if (cFunction == null) {
- return null;
- }
-
- HighFunction highFunction = cFunction.getHighFunction();
- if (highFunction == null) {
- return null;
- }
- return highFunction.getFunction();
- }
-
/**
* Returns true if there are not highlights
* @return true if there are not highlights
@@ -98,23 +82,6 @@ public class TokenHighlights implements Iterable {
return highlightsByToken.get(getKey(t));
}
- /**
- * Returns all highlights for the given function
- *
- * @param f the function
- * @return the highlights
- */
- public Set getHighlightsByFunction(Function f) {
- Set results = new HashSet<>();
- Set keys = getHighlightKeys(f);
- for (TokenKey key : keys) {
- HighlightToken hl = highlightsByToken.get(key);
- results.add(hl);
- }
-
- return results;
- }
-
/**
* Returns true if this class has a highlight for the given token
* @param t the token
@@ -139,39 +106,6 @@ public class TokenHighlights implements Iterable {
highlightsByToken.remove(getKey(t));
}
- /**
- * Removes all highlights associated with the given function
- *
- * @param function the function
- * @return the removed highlights; empty if no highlights existed
- */
- public Set removeHighlightsByFunction(Function function) {
- Set oldHighlights = new HashSet<>();
- Set keys = getHighlightKeys(function);
- for (TokenKey key : keys) {
- HighlightToken hl = highlightsByToken.remove(key);
- oldHighlights.add(hl);
- }
-
- return oldHighlights;
- }
-
- private Set getHighlightKeys(Function function) {
- Set results = new HashSet<>();
-
- Set> entries = highlightsByToken.entrySet();
- for (Entry entry : entries) {
- HighlightToken highlight = entry.getValue();
- ClangToken token = highlight.getToken();
- Function tokenFunction = getFunction(token);
- if (function.equals(tokenFunction)) {
- results.add(entry.getKey());
- }
- }
-
- return results;
- }
-
@Override
public Iterator iterator() {
return highlightsByToken.values().iterator();
@@ -181,80 +115,4 @@ public class TokenHighlights implements Iterable {
public String toString() {
return highlightsByToken.values().toString();
}
-
-//==================================================================================================
-// Inner Classes
-//==================================================================================================
-
- // a key that allows us to equate tokens that are not the same instance
- private class TokenKey {
- private ClangToken token;
-
- TokenKey(ClangToken token) {
- this.token = Objects.requireNonNull(token);
- }
-
- public TokenKey(HighlightToken t) {
- this(t.getToken());
- }
-
- @Override
- public int hashCode() {
- String text = token.getText();
- return text == null ? 0 : text.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
-
- if (getClass() != obj.getClass()) {
- return false;
- }
-
- ClangToken otherToken = ((TokenKey) obj).token;
- if (token.getClass() != otherToken.getClass()) {
- return false;
- }
-
- if (!Objects.equals(token.getText(), otherToken.getText())) {
- return false;
- }
-
- ClangLine lineParent = token.getLineParent();
- ClangLine otherLineParent = otherToken.getLineParent();
- if (!sameLines(lineParent, otherLineParent)) {
- return false;
- }
- if (lineParent == null) {
- return false;
- }
-
- int positionInLine = lineParent.indexOfToken(token);
- int otherPositionInLine = otherLineParent.indexOfToken(otherToken);
- return positionInLine == otherPositionInLine;
- }
-
- private boolean sameLines(ClangLine l1, ClangLine l2) {
-
- if (l1 == null) {
- if (l2 != null) {
- return false;
- }
- return true;
- }
- else if (l2 == null) {
- return false;
- }
-
- return l1.getLineNumber() == l2.getLineNumber();
- }
-
- @Override
- public String toString() {
- return token.toString();
- }
- }
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenKey.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenKey.java
new file mode 100644
index 0000000000..ee57b2bf6e
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenKey.java
@@ -0,0 +1,93 @@
+/* ###
+ * 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.util.Objects;
+
+import ghidra.app.decompiler.ClangLine;
+import ghidra.app.decompiler.ClangToken;
+
+// a key that allows us to equate tokens that are not the same instance
+class TokenKey {
+ private ClangToken token;
+
+ TokenKey(ClangToken token) {
+ this.token = Objects.requireNonNull(token);
+ }
+
+ public TokenKey(HighlightToken t) {
+ this(t.getToken());
+ }
+
+ @Override
+ public int hashCode() {
+ String text = token.getText();
+ return text == null ? 0 : text.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ ClangToken otherToken = ((TokenKey) obj).token;
+ if (token.getClass() != otherToken.getClass()) {
+ return false;
+ }
+
+ if (!Objects.equals(token.getText(), otherToken.getText())) {
+ return false;
+ }
+
+ ClangLine lineParent = token.getLineParent();
+ ClangLine otherLineParent = otherToken.getLineParent();
+ if (!sameLines(lineParent, otherLineParent)) {
+ return false;
+ }
+ if (lineParent == null) {
+ return false;
+ }
+
+ int positionInLine = lineParent.indexOfToken(token);
+ int otherPositionInLine = otherLineParent.indexOfToken(otherToken);
+ return positionInLine == otherPositionInLine;
+ }
+
+ private boolean sameLines(ClangLine l1, ClangLine l2) {
+
+ if (l1 == null) {
+ if (l2 != null) {
+ return false;
+ }
+ return true;
+ }
+ else if (l2 == null) {
+ return false;
+ }
+
+ return l1.getLineNumber() == l2.getLineNumber();
+ }
+
+ @Override
+ public String toString() {
+ return token.toString();
+ }
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
index 9dd0eb1336..bae8e14046 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
@@ -20,6 +20,8 @@ import java.util.*;
import org.jdom.Element;
import ghidra.app.CorePluginPackage;
+import ghidra.app.decompiler.ClangToken;
+import ghidra.app.decompiler.DecompilerHighlightService;
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
@@ -49,6 +51,7 @@ import ghidra.util.task.SwingUpdateManager;
GoToService.class, NavigationHistoryService.class, ClipboardService.class,
DataTypeManagerService.class /*, ProgramManager.class */
},
+ servicesProvided = { DecompilerHighlightService.class },
eventsConsumed = {
ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class,
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
@@ -81,6 +84,12 @@ public class DecompilePlugin extends Plugin {
disconnectedProviders = new ArrayList<>();
connectedProvider = new PrimaryDecompilerProvider(this);
+
+ registerServices();
+ }
+
+ private void registerServices() {
+ registerServiceProvided(DecompilerHighlightService.class, connectedProvider);
}
@Override
@@ -200,15 +209,18 @@ public class DecompilePlugin extends Plugin {
}
}
+ void handleTokenRenamed(ClangToken tokenAtCursor, String newName) {
+ connectedProvider.handleTokenRenamed(tokenAtCursor, newName);
+ for (DecompilerProvider provider : disconnectedProviders) {
+ provider.handleTokenRenamed(tokenAtCursor, newName);
+ }
+ }
+
private void removeProvider(DecompilerProvider provider) {
tool.removeComponentProvider(provider);
provider.dispose();
}
- /**
- * Process the plugin event; delegates the processing to the
- * byte block.
- */
@Override
public void processEvent(PluginEvent event) {
if (event instanceof ProgramClosedPluginEvent) {
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
index 336cdbd07e..714a4ec756 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
@@ -15,7 +15,6 @@
*/
package ghidra.app.plugin.core.decompile;
-import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
@@ -55,11 +54,13 @@ import resources.ResourceManager;
import utility.function.Callback;
public class DecompilerProvider extends NavigatableComponentProviderAdapter
- implements DomainObjectListener, OptionsChangeListener, DecompilerCallbackHandler {
- final static String OPTIONS_TITLE = "Decompiler";
+ implements DomainObjectListener, OptionsChangeListener, DecompilerCallbackHandler,
+ DecompilerHighlightService {
- private static Icon REFRESH_ICON = Icons.REFRESH_ICON;
- static final ImageIcon C_SOURCE_ICON =
+ private static final String OPTIONS_TITLE = "Decompiler";
+
+ private static final Icon REFRESH_ICON = Icons.REFRESH_ICON;
+ private static final ImageIcon C_SOURCE_ICON =
ResourceManager.loadImage("images/decompileFunction.gif");
private DockingAction graphASTControlFlowAction;
@@ -264,6 +265,20 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
tool.toFront(this);
}
+//==================================================================================================
+// DecompilerHighlightService interface methods
+//==================================================================================================
+
+ @Override
+ public DecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
+ return getDecompilerPanel().createHighlighter(tm);
+ }
+
+ @Override
+ public DecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
+ return getDecompilerPanel().createHighlighter(id, tm);
+ }
+
//==================================================================================================
// DomainObjectListener methods
//==================================================================================================
@@ -360,6 +375,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
if (clipboardService != null) {
clipboardService.deRegisterClipboardContentProvider(clipboardProvider);
}
+
controller.dispose();
program = null;
currentLocation = null;
@@ -486,7 +502,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
decompilerPanel.setCursorPosition(location);
}
- DecompilerController getController() {
+ public DecompilerController getController() {
return controller;
}
@@ -644,6 +660,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
return controller.getDecompilerPanel();
}
+ // snapshot callback
public void cloneWindow() {
DecompilerProvider newProvider = plugin.createNewDisconnectedProvider();
@@ -656,16 +673,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
// Any change in the HighlightTokens should be delivered to the new panel
DecompilerPanel myPanel = getDecompilerPanel();
- TokenHighlights myHighlights = myPanel.getSecondaryHighlightedTokens();
newProvider.setLocation(currentLocation, myPanel.getViewerPosition());
// transfer any state after the new decompiler is initialized
DecompilerPanel newPanel = newProvider.getDecompilerPanel();
- Map highlightsByName = myHighlights.copyHighlightsByName();
newProvider.doWheNotBusy(() -> {
-
newPanel.setViewerPosition(myViewPosition);
- newPanel.applySecondaryHighlights(highlightsByName);
+ newPanel.cloneHighlights(myPanel);
});
});
}
@@ -1073,4 +1087,12 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
public void programClosed(Program closedProgram) {
controller.programClosed(closedProgram);
}
+
+ public void tokenRenamed(ClangToken tokenAtCursor, String newName) {
+ plugin.handleTokenRenamed(tokenAtCursor, newName);
+ }
+
+ void handleTokenRenamed(ClangToken tokenAtCursor, String newName) {
+ controller.getDecompilerPanel().tokenRenamed(tokenAtCursor, newName);
+ }
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AbstractSetSecondaryHighlightAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AbstractSetSecondaryHighlightAction.java
index 2e818dcbd5..7a43beb5bc 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AbstractSetSecondaryHighlightAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AbstractSetSecondaryHighlightAction.java
@@ -16,7 +16,7 @@
package ghidra.app.plugin.core.decompile.actions;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.TokenHighlights;
+import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
@@ -35,17 +35,12 @@ public abstract class AbstractSetSecondaryHighlightAction extends AbstractDecomp
return false;
}
- ClangToken tokenAtCursor = context.getTokenAtCursor();
- if (tokenAtCursor == null) {
+ ClangToken token = context.getTokenAtCursor();
+ if (token == null) {
return false;
}
- TokenHighlights highlightedTokens =
- context.getDecompilerPanel().getSecondaryHighlightedTokens();
- if (highlightedTokens.contains(tokenAtCursor)) {
- return false; // already highlighted
- }
-
- return true;
+ DecompilerPanel panel = context.getDecompilerPanel();
+ return !panel.hasSecondaryHighlight(token);
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceAction.java
index 403403861a..1642c92bee 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceAction.java
@@ -60,7 +60,7 @@ public class BackwardsSliceAction extends AbstractDecompilerAction {
Set backwardSlice = DecompilerUtils.getBackwardSlice(varnode);
SliceHighlightColorProvider colorProvider =
new SliceHighlightColorProvider(decompilerPanel, backwardSlice, varnode, op);
- decompilerPanel.addVarnodeHighlights(backwardSlice, colorProvider);
+ decompilerPanel.addHighlights(backwardSlice, colorProvider);
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceToPCodeOpsAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceToPCodeOpsAction.java
index 970078b26c..77e5cc96c4 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceToPCodeOpsAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/BackwardsSliceToPCodeOpsAction.java
@@ -55,7 +55,7 @@ public class BackwardsSliceToPCodeOpsAction extends AbstractDecompilerAction {
}
DecompilerPanel decompilerPanel = context.getDecompilerPanel();
decompilerPanel.clearPrimaryHighlights();
- decompilerPanel.addPcodeOpHighlights(backwardSlice,
+ decompilerPanel.addHighlights(backwardSlice,
decompilerPanel.getCurrentVariableHighlightColor());
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceAction.java
index 211b5846d3..3b667631c8 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceAction.java
@@ -57,7 +57,7 @@ public class ForwardSliceAction extends AbstractDecompilerAction {
SliceHighlightColorProvider colorProvider =
new SliceHighlightColorProvider(decompilerPanel, forwardSlice, varnode, op);
- decompilerPanel.addVarnodeHighlights(forwardSlice, colorProvider);
+ decompilerPanel.addHighlights(forwardSlice, colorProvider);
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceToPCodeOpsAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceToPCodeOpsAction.java
index fb3b9ff42a..5d8d27307f 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceToPCodeOpsAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ForwardSliceToPCodeOpsAction.java
@@ -55,7 +55,7 @@ public class ForwardSliceToPCodeOpsAction extends AbstractDecompilerAction {
}
DecompilerPanel decompilerPanel = context.getDecompilerPanel();
decompilerPanel.clearPrimaryHighlights();
- decompilerPanel.addPcodeOpHighlights(forwardSlice,
+ decompilerPanel.addHighlights(forwardSlice,
decompilerPanel.getCurrentVariableHighlightColor());
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/HighlightDefinedUseAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/HighlightDefinedUseAction.java
index 67ccef0598..2833fec26e 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/HighlightDefinedUseAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/HighlightDefinedUseAction.java
@@ -57,7 +57,7 @@ public class HighlightDefinedUseAction extends AbstractDecompilerAction {
PcodeOp op = varnode.getDef();
SliceHighlightColorProvider colorProvider =
new SliceHighlightColorProvider(decompilerPanel, varnodes, varnode, op);
- decompilerPanel.addVarnodeHighlights(varnodes, colorProvider);
+ decompilerPanel.addHighlights(varnodes, colorProvider);
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableAction.java
index 23ca493f0b..28862182e4 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableAction.java
@@ -80,7 +80,7 @@ public class IsolateVariableAction extends AbstractDecompilerAction {
HighSymbol highSymbol = tokenAtCursor.getHighVariable().getSymbol();
IsolateVariableTask newVariableTask =
new IsolateVariableTask(context.getTool(), context.getProgram(),
- context.getDecompilerPanel(), tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
+ context.getComponentProvider(), tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
newVariableTask.runTask(false);
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableTask.java
index 905b1134d8..1ca6d40c68 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableTask.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/IsolateVariableTask.java
@@ -16,7 +16,7 @@
package ghidra.app.plugin.core.decompile.actions;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.DecompilerPanel;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
@@ -37,9 +37,9 @@ public class IsolateVariableTask extends RenameTask {
private boolean nameIsReserved;
private boolean instanceIsMapped;
- public IsolateVariableTask(PluginTool tool, Program program, DecompilerPanel panel,
+ public IsolateVariableTask(PluginTool tool, Program program, DecompilerProvider provider,
ClangToken token, HighSymbol sym, SourceType st) {
- super(tool, program, panel, token, "");
+ super(tool, program, provider, token, "");
highSymbol = sym;
highFunction = highSymbol.getHighFunction();
function = highFunction.getFunction();
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java
index 2e4d643834..822fa1afd3 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java
@@ -16,7 +16,8 @@
package ghidra.app.plugin.core.decompile.actions;
import docking.action.MenuData;
-import ghidra.app.decompiler.component.*;
+import ghidra.app.decompiler.component.ClangHighlightController;
+import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
@@ -45,8 +46,7 @@ public class RemoveAllSecondaryHighlightsAction extends AbstractDecompilerAction
}
DecompilerPanel panel = context.getDecompilerPanel();
- TokenHighlights highlightedTokens = panel.getSecondaryHighlightedTokens();
- return !highlightedTokens.isEmpty();
+ return panel.hasSecondaryHighlights();
}
@Override
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java
index b8d94eccd6..d44080c654 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java
@@ -17,7 +17,8 @@ package ghidra.app.plugin.core.decompile.actions;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.*;
+import ghidra.app.decompiler.component.ClangHighlightController;
+import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
@@ -51,8 +52,7 @@ public class RemoveSecondaryHighlightAction extends AbstractDecompilerAction {
}
DecompilerPanel panel = context.getDecompilerPanel();
- TokenHighlights highlightedTokens = panel.getSecondaryHighlightedTokens();
- return highlightedTokens.contains(token);
+ return panel.hasSecondaryHighlight(token);
}
@Override
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameFieldAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameFieldAction.java
index 6f59db739b..30ae9c7da1 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameFieldAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameFieldAction.java
@@ -58,22 +58,23 @@ public class RenameFieldAction extends AbstractDecompilerAction {
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
PluginTool tool = context.getTool();
- final ClangToken tokenAtCursor = context.getTokenAtCursor();
-
+ ClangToken tokenAtCursor = context.getTokenAtCursor();
Structure dt = getStructDataType(tokenAtCursor);
if (dt == null) {
Msg.showError(this, tool.getToolFrame(), "Rename Failed",
"Could not find structure datatype");
return;
}
+
int offset = ((ClangFieldToken) tokenAtCursor).getOffset();
if (offset < 0 || offset >= dt.getLength()) {
Msg.showError(this, tool.getToolFrame(), "Rename Failed",
"Could not resolve field within structure");
return;
}
+
RenameStructureFieldTask nameTask =
- new RenameStructureFieldTask(tool, context.getProgram(), context.getDecompilerPanel(),
+ new RenameStructureFieldTask(tool, context.getProgram(), context.getComponentProvider(),
tokenAtCursor, dt, offset);
nameTask.runTask(true);
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameLocalAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameLocalAction.java
index 3eb38c7650..b7a766fc75 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameLocalAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameLocalAction.java
@@ -81,8 +81,7 @@ public class RenameLocalAction extends AbstractDecompilerAction {
HighSymbol highSymbol = findHighSymbolFromToken(tokenAtCursor, context.getHighFunction());
RenameVariableTask nameTask = new RenameVariableTask(tool, context.getProgram(),
- context.getDecompilerPanel(),
- tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
+ context.getComponentProvider(), tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
nameTask.runTask(true);
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameStructureFieldTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameStructureFieldTask.java
index 08024495b4..df414a9e10 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameStructureFieldTask.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameStructureFieldTask.java
@@ -16,7 +16,7 @@
package ghidra.app.plugin.core.decompile.actions;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.DecompilerPanel;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
@@ -29,9 +29,9 @@ public class RenameStructureFieldTask extends RenameTask {
private Structure structure;
public int offset;
- public RenameStructureFieldTask(PluginTool tool, Program program, DecompilerPanel panel,
+ public RenameStructureFieldTask(PluginTool tool, Program program, DecompilerProvider provider,
ClangToken token, Structure structure, int offset) {
- super(tool, program, panel, token, token.getText());
+ super(tool, program, provider, token, token.getText());
this.structure = structure;
this.offset = offset;
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameTask.java
index 32f746b593..f23f568178 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameTask.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameTask.java
@@ -15,10 +15,12 @@
*/
package ghidra.app.plugin.core.decompile.actions;
+import org.apache.commons.lang3.StringUtils;
+
import docking.widgets.dialogs.InputDialog;
import docking.widgets.dialogs.InputDialogListener;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.DecompilerPanel;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
@@ -37,37 +39,39 @@ public abstract class RenameTask {
protected String errorMsg = null; // Error to return if isValid returns false
protected PluginTool tool;
protected Program program;
- protected DecompilerPanel decompilerPanel;
+ protected DecompilerProvider provider;
protected ClangToken tokenAtCursor;
-
- public RenameTask(PluginTool tool, Program program, DecompilerPanel panel, ClangToken token,
- String old) {
+
+ public RenameTask(PluginTool tool, Program program, DecompilerProvider provider,
+ ClangToken token, String old) {
this.tool = tool;
this.program = program;
- this.decompilerPanel = panel;
+ this.provider = provider;
this.tokenAtCursor = token;
oldName = old;
}
-
+
public abstract String getTransactionName();
-
+
public abstract boolean isValid(String newNm);
-
+
public abstract void commit() throws DuplicateNameException, InvalidInputException;
-
- public String getNewName() { return newName; }
-
+
+ public String getNewName() {
+ return newName;
+ }
+
/**
* Bring up a dialog that is initialized with the old name, and allows the user to select a new name
* @param oldNameIsCancel is true if the user keeping/entering the old name is considered a cancel
* @return true unless the user canceled
*/
- private boolean runDialog(boolean oldNameIsCancel) {
+ private boolean showDialog(boolean oldNameIsCancel) {
InputDialogListener listener = new InputDialogListener() {
@Override
public boolean inputIsValid(InputDialog dialog) {
String name = dialog.getValue();
- if ((name==null)||(name.length()==0)) {
+ if (StringUtils.isBlank(name)) {
dialog.setStatusText("Cannot have empty name");
return false;
}
@@ -82,21 +86,21 @@ public abstract class RenameTask {
return res;
}
};
-
+
String label = "Rename " + oldName + ":";
- InputDialog renameVarDialog = new InputDialog( getTransactionName(),
- new String[]{ label }, new String[]{ oldName }, listener );
-
- tool.showDialog(renameVarDialog);
-
- if (renameVarDialog.isCanceled()) {
- return false;
- }
+ InputDialog renameVarDialog = new InputDialog(getTransactionName(),
+ new String[] { label }, new String[] { oldName }, listener);
+
+ tool.showDialog(renameVarDialog);
+
+ if (renameVarDialog.isCanceled()) {
+ return false;
+ }
if (oldNameIsCancel && newName.equals(oldName)) {
return false;
}
- return true;
-
+ return true;
+
}
/**
@@ -104,23 +108,28 @@ public abstract class RenameTask {
* @param oldNameIsCancel is true if the user entering/keeping the old name is considered a cancel
*/
public void runTask(boolean oldNameIsCancel) {
- boolean dialogres = runDialog(oldNameIsCancel);
- if (dialogres) {
- int transaction = program.startTransaction(getTransactionName());
- boolean commit = false;
- try {
- commit();
- commit = true;
- }
- catch (DuplicateNameException e) {
- Msg.showError(this, tool.getToolFrame(), "Rename Failed", e.getMessage());
- }
- catch (InvalidInputException e) {
- Msg.showError(this, tool.getToolFrame(), "Rename Failed", e.getMessage());
- }
- finally {
- program.endTransaction(transaction, commit);
- decompilerPanel.tokenRenamed(tokenAtCursor, getNewName());
+ boolean result = showDialog(oldNameIsCancel);
+ if (!result) {
+ return;
+ }
+
+ int tx = program.startTransaction(getTransactionName());
+ boolean commit = false;
+ try {
+ commit();
+ commit = true;
+ }
+ catch (DuplicateNameException e) {
+ Msg.showError(this, tool.getToolFrame(), "Rename Failed", e.getMessage());
+ }
+ catch (InvalidInputException e) {
+ Msg.showError(this, tool.getToolFrame(), "Rename Failed", e.getMessage());
+ }
+ finally {
+ program.endTransaction(tx, commit);
+
+ if (commit) {
+ provider.tokenRenamed(tokenAtCursor, getNewName());
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameVariableTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameVariableTask.java
index da946cf53e..dd31319cee 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameVariableTask.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RenameVariableTask.java
@@ -16,7 +16,7 @@
package ghidra.app.plugin.core.decompile.actions;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.DecompilerPanel;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
@@ -35,9 +35,9 @@ public class RenameVariableTask extends RenameTask {
private SourceType srctype; // Desired source type for the variable being renamed
private SourceType signatureSrcType; // Signature source type of the function (which will be preserved)
- public RenameVariableTask(PluginTool tool, Program program, DecompilerPanel panel,
+ public RenameVariableTask(PluginTool tool, Program program, DecompilerProvider provider,
ClangToken token, HighSymbol sym, SourceType st) {
- super(tool, program, panel, token, sym.getName());
+ super(tool, program, provider, token, sym.getName());
highSymbol = sym;
exactSpot = token.getVarnode();
hfunction = sym.getHighFunction();
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java
index 8ed4385ba0..81bacbcc4e 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java
@@ -19,8 +19,7 @@ import java.awt.Color;
import java.util.Set;
import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.component.DecompilerPanel;
-import ghidra.app.decompiler.component.DecompilerUtils;
+import ghidra.app.decompiler.component.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
@@ -30,7 +29,7 @@ import ghidra.program.model.pcode.Varnode;
* @see ForwardSliceAction
* @see BackwardsSliceAction
*/
-public class SliceHighlightColorProvider implements TokenHighlightColorProvider {
+public class SliceHighlightColorProvider implements ColorProvider {
private Set varnodes;
private Varnode specialVn;
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java
similarity index 55%
rename from Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java
rename to Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java
index d8acb778f7..794af85ad5 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package ghidra.app.plugin.core.decompile;
+package ghidra.app.decompiler.component;
import static org.junit.Assert.*;
@@ -36,10 +36,10 @@ import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.cmd.comments.SetCommentCmd;
-import ghidra.app.decompiler.ClangLine;
-import ghidra.app.decompiler.ClangToken;
+import ghidra.app.decompiler.*;
import ghidra.app.decompiler.DecompileOptions.NamespaceStrategy;
-import ghidra.app.decompiler.component.*;
+import ghidra.app.plugin.core.decompile.AbstractDecompilerTest;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.decompile.actions.*;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.util.OptionsService;
@@ -66,7 +66,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
public void testClangTextField_getTokenIndex() {
/*
- 1|
+ 1|
2| int _main(int argc,char **argv)
3|
4| {
@@ -115,7 +115,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
public void testClangTextField_getNextTokenIndex() {
/*
- 1|
+ 1|
2| int _main(int argc,char **argv)
3|
4| {
@@ -938,10 +938,718 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
assertAllFieldsSecondaryHighlighted(token, color);
}
+ @Test
+ public void testHighlightService() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertTrue(spyMatcher.getMatchingTokens().size() > 0);
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ highlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ }
+
+ @Test
+ public void testHighlightService_WithPrimaryHighlights() {
+
+ /*
+
+ 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:7 "_printf | ("..."
+ int line = 5;
+ int charPosition = 7;
+ setDecompilerLocation(line, charPosition);
+ assertPrimaryHighlights("(\"call_structure_A: %s\\n\",a->name)");
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+ assertPrimaryHighlights("(\"call_structure_A: %s\\n\",a->name)");
+
+ highlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertPrimaryHighlights("(\"call_structure_A: %s\\n\",a->name)");
+ }
+
+ @Test
+ public void testHighlightService_WithSecondaryighlights_NoOverlappingMatches() {
+
+ /*
+
+ 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'
+
+ //
+ // This test will add a 'secondary highlight' to the "_printf" token and will also use the
+ // highlight service to add a highlighter highlight for that same token
+ //
+
+ // 5:38 "name"
+ int line = 5;
+ int charPosition = 38;
+ setDecompilerLocation(line, charPosition);
+
+ ClangToken secondrayToken = getToken();
+ String text = secondrayToken.getText();
+ assertEquals("name", text);
+
+ Color secondaryHlColor = highlight();
+ assertAllFieldsSecondaryHighlighted(secondrayToken, secondaryHlColor);
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+ assertAllFieldsSecondaryHighlighted(secondrayToken, secondaryHlColor);
+
+ highlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertAllFieldsSecondaryHighlighted(secondrayToken, secondaryHlColor);
+ }
+
+ @Test
+ public void testHighlightService_WithSecondaryighlights_WithOverlappingMatches() {
+
+ /*
+
+ 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'
+
+ //
+ // This test will add a 'secondary highlight' to the "_printf" token and will also use the
+ // highlight service to add a highlighter highlight for that same token
+ //
+
+ // 5:2 "_printf"
+ int line = 5;
+ int charPosition = 2;
+ setDecompilerLocation(line, charPosition);
+
+ ClangToken secondaryToken = getToken();
+ String secondaryText = secondaryToken.getText();
+ assertEquals("_printf", secondaryText);
+
+ Color secondaryHlColor = highlight();
+ assertAllFieldsSecondaryHighlighted(secondaryToken, secondaryHlColor);
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ Color combinedColor = getCombinedHighlightColor(secondaryToken);
+ ColorMatcher cm = new ColorMatcher(hlColor, secondaryHlColor, combinedColor);
+ Predicate ignore = t -> t == secondaryToken;
+ assertAllSecondaryAndHighlighterFieldsHighlighted(provider, hlText, cm, ignore);
+
+ highlighter.clearHighlights();
+ assertAllFieldsSecondaryHighlighted(secondaryToken, secondaryHlColor);
+ }
+
+ @Test
+ public void testHighlightService_MultipleHighlighters_NoOverlappingMatches() {
+
+ /*
+
+ 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'
+
+ String hlText1 = "_printf";
+ Color hlColor1 = Color.PINK;
+ CTokenHighlightMatcher hlMatcher1 = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText1)) {
+ return hlColor1;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher1 = new SpyCTokenHighlightMatcher(hlMatcher1);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter1 = hlService.createHighlighter(spyMatcher1);
+ highlighter1.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher1, hlText1, hlColor1);
+
+ highlighter1.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText1);
+
+ String hlText2 = "name";
+ Color hlColor2 = Color.GREEN;
+ CTokenHighlightMatcher hlMatcher2 = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText2)) {
+ return hlColor2;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher2 = new SpyCTokenHighlightMatcher(hlMatcher2);
+ DecompilerHighlighter highlighter2 = hlService.createHighlighter(spyMatcher2);
+ highlighter2.applyHighlights();
+
+ Color combinedColor = getBlendedColor(hlColor1, hlColor2);
+ assertAllHighlighterFieldsHighlighted(spyMatcher1, hlText1, combinedColor);
+ assertAllHighlighterFieldsHighlighted(spyMatcher2, hlText2, combinedColor);
+
+ highlighter1.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText1);
+ assertAllHighlighterFieldsHighlighted(spyMatcher2, hlText2, combinedColor);
+
+ highlighter2.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText2);
+ }
+
+ @Test
+ public void testHighlightService_MultipleHighlighters_WithOverlappingMatches() {
+
+ /*
+
+ 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:7 "_printf | ("..."
+ int line = 5;
+ int charPosition = 7;
+ setDecompilerLocation(line, charPosition);
+ assertPrimaryHighlights("(\"call_structure_A: %s\\n\",a->name)");
+
+ String hlText = "_printf";
+ Color hlColor1 = Color.PINK;
+ CTokenHighlightMatcher hlMatcher1 = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor1;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher1 = new SpyCTokenHighlightMatcher(hlMatcher1);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter1 = hlService.createHighlighter(spyMatcher1);
+ highlighter1.applyHighlights();
+
+ Color hlColor2 = Color.GREEN;
+ CTokenHighlightMatcher hlMatcher2 = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor2;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher2 = new SpyCTokenHighlightMatcher(hlMatcher2);
+ DecompilerHighlighter highlighter2 = hlService.createHighlighter(spyMatcher2);
+ highlighter2.applyHighlights();
+
+ Color combinedColor = getBlendedColor(hlColor1, hlColor2);
+ assertAllHighlighterFieldsHighlighted(spyMatcher1, hlText, combinedColor);
+ assertAllHighlighterFieldsHighlighted(spyMatcher2, hlText, combinedColor);
+ assertPrimaryHighlights("(\"call_structure_A: %s\\n\",a->name)");
+
+ highlighter1.clearHighlights();
+ assertAllHighlighterFieldsHighlighted(spyMatcher2, hlText, hlColor2);
+
+ highlighter2.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertPrimaryHighlights("(\"call_structure_A: %s\\n\",a->name)");
+ }
+
+ @Test
+ public void testHighlightService_Dispose() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ highlighter.dispose();
+ assertNoFieldsSecondaryHighlighted(hlText);
+
+ // no effect calling apply after dispose
+ highlighter.applyHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ }
+
+ @Test
+ public void testHighlightService_CreateWithId() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher1 = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ String id = "TestId";
+ DecompilerHighlighter highlighter = hlService.createHighlighter(id, spyMatcher1);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher1, hlText, hlColor);
+
+ highlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+
+ SpyCTokenHighlightMatcher spyMatcher2 = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlighter newHighlighter = hlService.createHighlighter(id, spyMatcher2);
+ newHighlighter.applyHighlights();
+ assertAllHighlighterFieldsHighlighted(spyMatcher2, hlText, hlColor);
+
+ newHighlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+
+ // make sure calls to the original highlighter no longer work, as it has been removed
+ spyMatcher1.clear();
+ highlighter.applyHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+
+ }
+
+ @Test
+ public void testHighlightService_CloneDecompiler_HighlighterApplied() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ DecompilerProvider clone = cloneDecompiler();
+ DecompilerHighlighter cloneHighlighter = getHighlighter(clone, highlighter.getId());
+ assertAllHighlighterFieldsHighlighted(clone, cloneHighlighter, spyMatcher, hlText, hlColor);
+
+ highlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertNoFieldsSecondaryHighlighted(clone, hlText);
+ }
+
+ @Test
+ public void testHighlightService_CloneDecompiler_HighlightsUpdated() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ DecompilerProvider clone = cloneDecompiler();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertNoFieldsSecondaryHighlighted(clone, hlText);
+
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+ DecompilerHighlighter cloneHighlighter = getHighlighter(clone, highlighter.getId());
+ assertAllHighlighterFieldsHighlighted(clone, cloneHighlighter, spyMatcher, hlText, hlColor);
+
+ highlighter.clearHighlights();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertNoFieldsSecondaryHighlighted(clone, hlText);
+ }
+
+ @Test
+ public void testHighlightService_CloneDecompiler_RemoveHighlighter() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ DecompilerProvider clone = cloneDecompiler();
+ DecompilerHighlighter cloneHighlighter = getHighlighter(clone, highlighter.getId());
+ assertAllHighlighterFieldsHighlighted(clone, cloneHighlighter, spyMatcher, hlText, hlColor);
+
+ highlighter.dispose();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ assertNoFieldsSecondaryHighlighted(clone, hlText);
+ }
+
+ @Test
+ public void testHighlightService_NewFunctionReappliesHighlights() {
+
+ /*
+
+ 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'
+
+ String hlText = "_printf";
+ Color hlColor = Color.PINK;
+ CTokenHighlightMatcher hlMatcher = new CTokenHighlightMatcher() {
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ if (token.getText().contains(hlText)) {
+ return hlColor;
+ }
+ return null;
+ }
+ };
+ SpyCTokenHighlightMatcher spyMatcher = new SpyCTokenHighlightMatcher(hlMatcher);
+ DecompilerHighlightService hlService = getHighlightService();
+ DecompilerHighlighter highlighter = hlService.createHighlighter(spyMatcher);
+ highlighter.applyHighlights();
+
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ spyMatcher.clear();
+
+ // this function also has calls to '_printf'
+ decompile("100000e10"); // '_call_structure_B'
+
+ assertTrue(spyMatcher.getMatchingTokens().size() > 0);
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, hlColor);
+
+ highlighter.dispose();
+ assertNoFieldsSecondaryHighlighted(hlText);
+ }
+
//==================================================================================================
// Private Methods
//==================================================================================================
+ private DecompilerHighlighter getHighlighter(DecompilerProvider clone, String id) {
+ DecompilerPanel clonePanel = clone.getController().getDecompilerPanel();
+ return clonePanel.getHighlighter(id);
+ }
+
+ private DecompilerHighlightService getHighlightService() {
+ return provider;
+ }
+
private void refresh() {
DockingActionIf action = getAction(decompiler, "Refresh");
@@ -1003,12 +1711,19 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
return c;
}
+ private Color getBlendedColor(Color... colors) {
+ DecompilerPanel panel = provider.getController().getDecompilerPanel();
+ ClangHighlightController highlightController = panel.getHighlightController();
+ List colorList = Arrays.asList(colors);
+ return highlightController.blend(colorList);
+ }
+
private Color getCombinedHighlightColor(ClangToken token) {
return getCombinedHighlightColor(provider, token);
}
private Color getCombinedHighlightColor(DecompilerProvider theProvider, ClangToken token) {
- DecompilerPanel panel = theProvider.getDecompilerPanel();
+ DecompilerPanel panel = theProvider.getController().getDecompilerPanel();
ClangHighlightController highlightController = panel.getHighlightController();
return highlightController.getCombinedColor(token);
}
@@ -1017,7 +1732,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
DecompilerController controller = provider.getController();
DecompilerPanel panel = controller.getDecompilerPanel();
ClangHighlightController highlightController = panel.getHighlightController();
- TokenHighlights tokens = highlightController.getPrimaryHighlightedTokens();
+ TokenHighlights tokens = highlightController.getPrimaryHighlights();
List results = new ArrayList<>();
for (HighlightToken hl : tokens) {
@@ -1087,17 +1802,22 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
DockingActionIf highlightAction = getAction(decompiler, SetSecondaryHighlightAction.NAME);
performAction(highlightAction, provider.getActionContext(null), true);
- HighlightToken ht = getSecondaryHighlight(token);
- assertNotNull("No highlight for token: " + token, ht);
- return ht.getColor();
+ Color color = getSecondaryHighlight(token);
+ assertNotNull("No highlight for token: " + token, color);
+ return color;
}
- private HighlightToken getSecondaryHighlight(ClangToken token) {
+ private Color getSecondaryHighlight(ClangToken token) {
DecompilerController controller = provider.getController();
DecompilerPanel panel = controller.getDecompilerPanel();
- TokenHighlights highlights = panel.getSecondaryHighlightedTokens();
- HighlightToken ht = highlights.get(token);
- return ht;
+ return panel.getSecondaryHighlight(token);
+ }
+
+ private TokenHighlights getHighligtedTokens(DecompilerProvider theProvider,
+ DecompilerHighlighter highlighter) {
+ DecompilerController controller = theProvider.getController();
+ DecompilerPanel panel = controller.getDecompilerPanel();
+ return panel.getHighlights(highlighter);
}
private void highlightWithColorChooser(Color color) {
@@ -1117,9 +1837,8 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
});
waitForSwing();
- HighlightToken ht = getSecondaryHighlight(token);
- assertNotNull("No highlight for token: " + token, ht);
- Color hlColor = ht.getColor();
+ Color hlColor = getSecondaryHighlight(token);
+ assertNotNull("No highlight for token: " + token, hlColor);
assertEquals(color, hlColor);
}
@@ -1131,8 +1850,8 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
getLocalAction(provider, RemoveSecondaryHighlightAction.NAME);
performAction(highlightAction, provider.getActionContext(null), true);
- HighlightToken ht = getSecondaryHighlight(token);
- assertNull("Token should not be highlighted - '" + token + "': ", ht);
+ Color color = getSecondaryHighlight(token);
+ assertNull("Token should not be highlighted - '" + token + "': ", color);
}
private void assertAllFieldsPrimaryHighlighted(String name) {
@@ -1145,10 +1864,40 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
assertAllFieldsHighlighted(name, cm, noIgnores);
}
+ private void assertAllHighlighterFieldsHighlighted(
+ SpyCTokenHighlightMatcher spyMatcher, String hlText, Color hlColor) {
+
+ ClangToken cursorToken = getToken(provider);
+ Predicate ignores = t -> t == cursorToken;
+ assertAllHighlighterFieldsHighlighted(spyMatcher, hlText, ignores);
+ }
+
+ private void assertAllHighlighterFieldsHighlighted(DecompilerProvider theProvider,
+ DecompilerHighlighter highlighter, SpyCTokenHighlightMatcher matcher, String matchText,
+ Color color) {
+
+ ClangToken cursorToken = getToken(theProvider);
+ Predicate ignores = t -> t == cursorToken;
+ assertAllHighlighterFieldsHighlighted(theProvider, highlighter, matcher, matchText,
+ ignores);
+
+ // test the token under the cursor directly, as that may have a combined highlight applied
+ Color combinedColor = getCombinedHighlightColor(theProvider, cursorToken);
+ ColorMatcher cm = new ColorMatcher(color, combinedColor);
+ Color actual = cursorToken.getHighlight();
+ assertTrue("Token is not highlighted: '" + cursorToken + "'" + "\n\texpected: " + cm +
+ "; found: " + toString(actual), cm.matches(actual));
+ }
+
private void assertAllFieldsSecondaryHighlighted(ClangToken token, Color color) {
- Predicate ignores = t -> t == token;
String name = token.getText();
- assertAllFieldsHighlighted(name, color, ignores);
+ assertAllFieldsSecondaryHighlighted(token, name, color);
+ }
+
+ private void assertAllFieldsSecondaryHighlighted(ClangToken token, String matchText,
+ Color color) {
+ Predicate ignores = t -> t == token;
+ assertAllFieldsHighlighted(matchText, color, ignores);
// test the token under the cursor directly, as that may have a combined highlight applied
Color combinedColor = getCombinedHighlightColor(token);
@@ -1158,14 +1907,18 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
"; found: " + toString(actual), cm.matches(actual));
}
- private void assertNoFieldsSecondaryHighlighted(String name) {
+ private void assertNoFieldsSecondaryHighlighted(String hlText) {
+ assertNoFieldsSecondaryHighlighted(provider, hlText);
+ }
+
+ private void assertNoFieldsSecondaryHighlighted(DecompilerProvider theProvider, String hlText) {
Color defaultHlColor = getDefaultHighlightColor();
Color specialHlColor = getSpecialHighlightColor();
Color middleMouseHlColor = getMiddleMouseHighlightColor();
ColorMatcher allowedColors =
new ColorMatcher(defaultHlColor, specialHlColor, middleMouseHlColor, null);
Predicate noIgnores = t -> false;
- assertAllFieldsHighlighted(name, allowedColors, noIgnores);
+ assertAllFieldsHighlighted(theProvider, hlText, allowedColors, noIgnores);
}
private void assertAllFieldsHighlighted(String name, Color hlColor) {
@@ -1178,7 +1931,54 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
Predicate ignore) {
ColorMatcher cm = new ColorMatcher(color);
- assertAllFieldsHighlighted(name, cm, ignore);
+ assertAllFieldsHighlighted(provider, name, cm, ignore);
+ }
+
+ private void assertAllHighlighterFieldsHighlighted(SpyCTokenHighlightMatcher spyMatcher,
+ String matchText, Predicate ignore) {
+
+ Map matchingTokens = spyMatcher.getMatchingTokens();
+ for (Map.Entry entry : matchingTokens.entrySet()) {
+
+ ClangToken token = entry.getKey();
+ Color color = entry.getValue();
+ Color combinedColor = getCombinedHighlightColor(token);
+ ColorMatcher cm = new ColorMatcher(color, combinedColor);
+ if (ignore.test(token)) {
+ continue;
+ }
+
+ Color actual = token.getHighlight();
+ assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " +
+ cm + "; found: " + toString(actual), cm.matches(actual));
+ }
+ }
+
+ private void assertAllHighlighterFieldsHighlighted(DecompilerProvider theProvider,
+ DecompilerHighlighter decompilerHighlighter, SpyCTokenHighlightMatcher spyMatcher,
+ String matchText, Predicate ignore) {
+
+ TokenHighlights providerHighlights =
+ getHighligtedTokens(theProvider, decompilerHighlighter);
+ assertNotNull("No highligts for highlighter in the given provider", providerHighlights);
+ Map matchingTokens = spyMatcher.getMatchingTokens();
+
+ for (Map.Entry entry : matchingTokens.entrySet()) {
+
+ ClangToken token = entry.getKey();
+ HighlightToken hlToken = providerHighlights.get(token);
+ assertNotNull("Provider is missing highlighted token", hlToken);
+ Color color = entry.getValue();
+ Color combinedColor = getCombinedHighlightColor(theProvider, token);
+ ColorMatcher cm = new ColorMatcher(color, combinedColor);
+ if (ignore.test(token)) {
+ continue;
+ }
+
+ Color actual = token.getHighlight();
+ assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " +
+ cm + "; found: " + toString(actual), cm.matches(actual));
+ }
}
private void assertAllFieldsHighlighted(String name, ColorMatcher colorMatcher,
@@ -1195,7 +1995,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
ColorMatcher cm = new ColorMatcher(color, combinedColor);
assertAllFieldsHighlighted(theProvider, name, cm, ignores);
- // test the token under the cursor directly, as that may have a combined highlight applied
+ // test the token under the cursor directly, as that may have a combined highlight applied
Color actual = token.getHighlight();
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
"; found: " + toString(actual), cm.matches(actual));
@@ -1218,6 +2018,26 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
}
}
+ private void assertAllSecondaryAndHighlighterFieldsHighlighted(DecompilerProvider theProvider,
+ String name, ColorMatcher colorMatcher, Predicate ignore) {
+
+ DecompilerController controller = theProvider.getController();
+ DecompilerPanel panel = controller.getDecompilerPanel();
+ List tokensWithName = panel.findTokensByName(name);
+ for (ClangToken otherToken : tokensWithName) {
+ if (ignore.test(otherToken)) {
+ continue;
+ }
+
+ Color actual = otherToken.getHighlight();
+ Color combinedColor = getCombinedHighlightColor(otherToken);
+ ColorMatcher combinedColorMatcher = colorMatcher.with(combinedColor);
+ assertTrue("Token is not highlighted: '" + otherToken + "'" + "\n\texpected: " +
+ combinedColorMatcher + "; found: " + toString(actual),
+ combinedColorMatcher.matches(actual));
+ }
+ }
+
private String toString(Color c) {
if (c == null) {
return "Color{null}";
@@ -1279,7 +2099,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
}
private void assertCurrentLocation(int line, int col) {
- DecompilerPanel panel = provider.getDecompilerPanel();
+ DecompilerPanel panel = provider.getController().getDecompilerPanel();
FieldLocation actual = panel.getCursorPosition();
FieldLocation expected = loc(line, col);
assertEquals("Decompiler cursor is not at the expected location", expected, actual);
@@ -1309,6 +2129,12 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
}
}
+ ColorMatcher with(Color c) {
+ ColorMatcher newCm = new ColorMatcher(c);
+ newCm.myColors.addAll(myColors);
+ return newCm;
+ }
+
public boolean matches(Color otherColor) {
for (Color c : myColors) {
if (Objects.equals(c, otherColor)) {
@@ -1329,4 +2155,31 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
//@formatter:on
}
}
+
+ private class SpyCTokenHighlightMatcher implements CTokenHighlightMatcher {
+
+ private CTokenHighlightMatcher delegate;
+ private Map highlightsByToken = new HashMap<>();
+
+ SpyCTokenHighlightMatcher(CTokenHighlightMatcher delegate) {
+ this.delegate = delegate;
+ }
+
+ Map getMatchingTokens() {
+ return highlightsByToken;
+ }
+
+ void clear() {
+ highlightsByToken.clear();
+ }
+
+ @Override
+ public Color getTokenHighlight(ClangToken token) {
+ Color hl = delegate.getTokenHighlight(token);
+ if (hl != null) {
+ highlightsByToken.put(token, hl);
+ }
+ return hl;
+ }
+ }
}
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java
index fa5fd30513..eef339ff2d 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java
@@ -84,7 +84,7 @@ public class HighSymbolTest extends AbstractDecompilerTest {
private void renameVariable(HighSymbol highSymbol, ClangToken tokenAtCursor, String newName) {
RenameVariableTask rename =
new RenameVariableTask(provider.getTool(), highSymbol.getProgram(),
- provider.getDecompilerPanel(), tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
+ provider, tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
assertTrue(rename.isValid(newName));
modifyProgram(p -> {
rename.commit();
@@ -94,7 +94,7 @@ public class HighSymbolTest extends AbstractDecompilerTest {
private void isolateVariable(HighSymbol highSymbol, ClangToken tokenAtCursor, String newName) {
IsolateVariableTask isolate = new IsolateVariableTask(provider.getTool(), program,
- provider.getDecompilerPanel(), tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
+ provider, tokenAtCursor, highSymbol, SourceType.USER_DEFINED);
assertTrue(isolate.isValid(newName));
modifyProgram(p -> {
isolate.commit();