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> 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: + *

+ * + *

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> 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> 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> 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> 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> tokens, + ColorProvider colorProvider) { + + Objects.requireNonNull(highlighter); + TokenHighlights highlighterTokens = + highlighterHighlights.computeIfAbsent(highlighter, k -> new TokenHighlights()); + addTokensToHighlights(tokens.get(), colorProvider, highlighterTokens); + } + + private void addPrimaryHighlights(Supplier> 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> 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> 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();