diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java index d0144e4f3c..c836ee0d3f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.hover; +import static ghidra.util.HTMLUtilities.HTML; + import java.awt.*; import javax.swing.JComponent; @@ -24,7 +26,7 @@ import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.support.FieldLocation; import ghidra.app.plugin.core.gotoquery.GoToHelper; import ghidra.app.services.CodeFormatService; -import ghidra.app.util.ToolTipUtils; +import ghidra.app.util.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.framework.options.Options; import ghidra.framework.plugintool.PluginTool; @@ -32,6 +34,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.*; +import ghidra.util.HTMLUtilities; import ghidra.util.bean.opteditor.OptionsVetoException; /** @@ -131,7 +134,6 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover { if (!enabled || programLocation == null || panel == null) { return null; } - panel.setProgram(program); Address refAddr = programLocation.getRefAddress(); if (refAddr != null && refAddr.isExternalAddress()) { @@ -140,22 +142,24 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover { previewLocation = getPreviewLocation(program, programLocation, programLocation.getRefAddress()); - - if (previewLocation != null) { - boolean toolTipForLocation = panel.goTo(previewLocation); - - // only continue if there was a valid location to go to - if (toolTipForLocation) { - - Rectangle bounds = panel.getBounds(); - bounds.x = WINDOW_OFFSET; - bounds.y = WINDOW_OFFSET; - panel.setBounds(bounds); - return panel; - } + if (previewLocation == null) { + return null; } - panel.setProgram(null); - return null; + + boolean validLocation = panel.goTo(previewLocation); + if (validLocation) { + panel.setProgram(program); + + Rectangle bounds = panel.getBounds(); + bounds.x = WINDOW_OFFSET; + bounds.y = WINDOW_OFFSET; + panel.setBounds(bounds); + return panel; + } + + // At this point we have a program location, but we could not go there. This can happen + // if the location is not in memory. + return createOutOfMemoryToolTipComponent(previewLocation); } protected JComponent createExternalToolTipComponent(Program program, Address extAddr) { @@ -183,6 +187,60 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover { return toolTip; } + protected JComponent createOutOfMemoryToolTipComponent(ProgramLocation location) { + + /* + Format + + Address: ram:1234 + Address not in memory + + + Or, when multiple symbols at destination + + + Address: ram:1234 + Symbols (3): + foo + bar + baz + Address not in memory + + */ + + String newline = HTMLUtilities.HTML_NEW_LINE; + StringBuilder buffy = new StringBuilder(HTML); + buffy.append("Address: "); + String addressString = location.getAddress().toString(true, false); + addressString = HTMLUtilities.friendlyEncodeHTML(addressString); + buffy.append(addressString); + buffy.append(newline); + + Program p = location.getProgram(); + SymbolTable st = p.getSymbolTable(); + Symbol[] symbols = st.getSymbols(location.getAddress()); + + if (symbols.length > 1) { + buffy.append("Symbols (").append(symbols.length).append("): ").append(newline); + String pad = HTMLUtilities.spaces(4); + SymbolInspector inspector = new SymbolInspector(tool, null); + for (Symbol s : symbols) { + ColorAndStyle style = inspector.getColorAndStyle(s); + String name = s.getName(true); + name = HTMLUtilities.friendlyEncodeHTML(pad); + String html = style.toHtml(name); + buffy.append(pad).append(html).append(newline); + } + } + + String message = "Address not in memory"; + message = HTMLUtilities.italic(message); + message = HTMLUtilities.colorString(Color.GRAY, message); + buffy.append(message); + toolTip.setTipText(buffy.toString()); + return toolTip; + } + public void programClosed(Program program) { if (panel != null && panel.getProgram() == program) { panel.setProgram(null); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ColorAndStyle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ColorAndStyle.java index 2038685dbb..fa2080cb90 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ColorAndStyle.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ColorAndStyle.java @@ -16,6 +16,9 @@ package ghidra.app.util; import java.awt.Color; +import java.awt.Font; + +import ghidra.util.HTMLUtilities; /** * A container class to hold a color and a style value. @@ -37,4 +40,36 @@ public class ColorAndStyle { public int getStyle() { return style; } + + public boolean isBold() { + return (style & Font.BOLD) != 0; + } + + public boolean isItalic() { + return (style & Font.ITALIC) != 0; + } + + /** + * Wraps the given text with HTML markup for each attribute and color defined by this + * class. The returned result will not be prepended with <HTML>. + * + * @param text the text to wrap + * @return the wrapped text + */ + public String toHtml(String text) { + + String html = text; + if (isBold()) { + html = HTMLUtilities.bold(html); + } + if (isItalic()) { + html = HTMLUtilities.italic(html); + } + + if (color != null) { + html = HTMLUtilities.colorString(color, html); + } + + return html; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java index 3820559879..edcdcd601c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java @@ -46,8 +46,9 @@ public class SymbolInspector implements OptionsChangeListener { private Map cache = new HashMap<>(); /** - * Constructs a new symbol inspector. - * It uses the tool to get the CATEGORY_BROWSER_DISPLAY options. + * Constructs a new symbol inspector + * It uses the tool to get the CATEGORY_BROWSER_DISPLAY options + * * @param serviceProvider a service provider for getting services * @param repaintComp the component to repaint when the options change */ @@ -56,8 +57,8 @@ public class SymbolInspector implements OptionsChangeListener { } /** - * Constructs a new symbol inspector. - * @param ownerPlugin the plugin which owns this instance (may be null) + * Constructs a new symbol inspector + * * @param options the options from which to get colors * @param repaintComp the component to repaint when the options change */ @@ -338,8 +339,9 @@ public class SymbolInspector implements OptionsChangeListener { } /** - * Get the ScreenElement corresponding to the type of the symbol. + * Get the ScreenElement corresponding to the type of the symbol * @param s symbol to inspect + * @return the screen element */ public ScreenElement getScreenElement(Symbol s) { @@ -447,7 +449,7 @@ public class SymbolInspector implements OptionsChangeListener { String optionName = se.getStyleOptionName(); Integer style = (Integer) cache.get(optionName); if (style == null) { - style = new Integer(optionsObject.getInt(se.getStyleOptionName(), -1)); + style = Integer.valueOf(optionsObject.getInt(se.getStyleOptionName(), -1)); cache.put(optionName, style); } return style.intValue(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index cb73bedebd..5a5889df68 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -15,6 +15,8 @@ */ package ghidra.test; +import static org.junit.Assert.assertNotNull; + import java.io.File; import java.io.IOException; import java.util.*; @@ -30,6 +32,7 @@ import ghidra.framework.cmd.Command; import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; +import ghidra.generic.function.*; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.*; @@ -102,7 +105,8 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock * If the language no longer exists, and suitable replacement language will be returned * if found. If no language is found, an exception will be thrown. * @param oldLanguageName old language name string - * @return + * @return the language compiler and spec + * @throws LanguageNotFoundException */ public static LanguageCompilerSpecPair getLanguageCompilerSpecPair(String oldLanguageName) throws LanguageNotFoundException { @@ -142,6 +146,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock * Creates an in-memory program with the given language * @param name the program name * @param languageString a language string of the format x86:LE:32:default + * @param compilerSpecID the ID * @param consumer a consumer for the program * @return a new program * @throws Exception if there is any issue creating the language @@ -174,8 +179,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock waitForSwing(); if (!status) { - Msg.error(AbstractGhidraHeadedIntegrationTest.class, - "Could not apply command: " + cmd.getStatusMsg()); + Msg.error(null, "Could not apply command: " + cmd.getStatusMsg()); } return status; @@ -189,6 +193,76 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } } + public static void tx(Program p, ExceptionalCallback c) throws E { + int txId = p.startTransaction("Test - Function in Transaction"); + boolean commit = true; + try { + c.call(); + p.flushEvents(); + waitForSwing(); + } + catch (RollbackException e) { + commit = false; + throw e; + } + finally { + p.endTransaction(txId, commit); + } + } + + /** + * Provides a convenient method for modifying the current program, handling the transaction + * logic + * + * @param program the program + * @param callback the code to execute + */ + public void modifyProgram(Program program, + ExceptionalConsumer callback) { + assertNotNull("Program cannot be null", program); + + boolean commit = false; + int tx = program.startTransaction("Test"); + try { + callback.accept(program); + commit = true; + } + catch (Exception e) { + failWithException("Exception modifying program '" + program.getName() + "'", e); + } + finally { + program.endTransaction(tx, commit); + } + } + + /** + * Provides a convenient method for modifying the current program, handling the transaction + * logic and returning a new item as a result + * + * @param program the program + * @param f the function for modifying the program and creating the desired result + * @return the result + */ + public R createInProgram(Program program, + ExceptionalFunction f) { + assertNotNull("Program cannot be null", program); + + R result = null; + boolean commit = false; + int tx = program.startTransaction("Test"); + try { + result = f.apply(program); + commit = true; + } + catch (Exception e) { + failWithException("Exception modifying program '" + program.getName() + "'", e); + } + finally { + program.endTransaction(tx, commit); + } + return result; + } + /** * Undo the last transaction on the domain object and wait for all events to be flushed. * @param dobj The domain object upon which to perform the undo. @@ -371,6 +445,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock * * @param program the program to search. * @param name the name of the symbol to find. + * @param namespace the parent namespace; may be null * @return the symbol with the given name if and only if it is the only one in that namespace */ public Symbol getUniqueSymbol(Program program, String name, Namespace namespace) { @@ -389,6 +464,8 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock * sleeping. * *

Do not leave this call in your test when committing changes. + * @param p the program + * @param address the address * * @throws Exception if there is an issue create a {@link TestEnv} */ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java index 21056f04a8..790bbacdbe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java @@ -25,6 +25,8 @@ import org.junit.After; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.framework.plugintool.PluginTool; +import ghidra.generic.function.ExceptionalConsumer; +import ghidra.generic.function.ExceptionalFunction; import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; @@ -68,6 +70,7 @@ public abstract class AbstractProgramBasedTest extends AbstractGhidraHeadedInteg * Override this method if you need to build your own program. * * @return the program to use for this test. + * @throws Exception if an exceptioun is thrown opening the program */ protected Program getProgram() throws Exception { return env.getProgram(getProgramName()); @@ -163,13 +166,13 @@ public abstract class AbstractProgramBasedTest extends AbstractGhidraHeadedInteg * * @param callback the code to execute */ - public void modifyProgram(ExceptionalCallback callback) { + public void modifyProgram(ExceptionalConsumer callback) { assertNotNull("Program cannot be null", program); boolean commit = false; int tx = program.startTransaction("Test"); try { - callback.call(program); + callback.accept(program); commit = true; } catch (Exception e) { @@ -187,7 +190,7 @@ public abstract class AbstractProgramBasedTest extends AbstractGhidraHeadedInteg * @param f the function for modifying the program and creating the desired result * @return the result */ - public R createInProgram(ExceptionalFunction f) { + public R createInProgram(ExceptionalFunction f) { assertNotNull("Program cannot be null", program); R result = null; @@ -205,18 +208,4 @@ public abstract class AbstractProgramBasedTest extends AbstractGhidraHeadedInteg } return result; } - -//================================================================================================== -// Inner Classes -//================================================================================================== - - // TODO promote this functional interface (do this later; there is an outstanding branch - // that has other functional interfaces with which to fraternize) - public interface ExceptionalCallback { - public void call(T t) throws Exception; - } - - public interface ExceptionalFunction { - public R apply(T t) throws Exception; - } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalCallback.java b/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalCallback.java new file mode 100644 index 0000000000..36741294d9 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalCallback.java @@ -0,0 +1,33 @@ +/* ### + * 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.generic.function; + +/** + * A generic functional interface that is more semantically sound than {@link Runnable}. Use + * anywhere you wish to have a generic callback function and you need to throw an exception. + * + * @param the exception of your choice + */ +@FunctionalInterface +public interface ExceptionalCallback { + + /** + * The method that will be called + * + * @throws Exception if the call throws an exception + */ + public void call() throws E; +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalConsumer.java b/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalConsumer.java new file mode 100644 index 0000000000..f39088f0a9 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalConsumer.java @@ -0,0 +1,35 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.generic.function; + +/** + * A generic functional interface that allows you to consume an item and potentially throw + * an exception. + * + * @param the input type + * @param the exception of your choice + */ +@FunctionalInterface +public interface ExceptionalConsumer { + + /** + * The method that will be called + * + * @param t the input + * @throws E if the call throws an exception + */ + public void accept(T t) throws E; +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalFunction.java b/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalFunction.java new file mode 100644 index 0000000000..82a0f2324d --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalFunction.java @@ -0,0 +1,37 @@ +/* ### + * 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.generic.function; + +/** + * A generic functional interface that allows you to consume an item, return a result, + * and potentially throw an exception. + * + * @param the input type + * @param the result type + * @param the exception of your choice + */ +@FunctionalInterface +public interface ExceptionalFunction { + + /** + * The method that will be called + * + * @param i the input + * @return the result of the call + * @throws E if the call throws an exception + */ + public R apply(I i) throws E; +}