diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html b/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html index f26dbaefc1..4b7946bd01 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html @@ -104,6 +104,11 @@ Notes: diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/comments/SetCommentsCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/comments/SetCommentsCmd.java index 5158505e05..1859f10aa5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/comments/SetCommentsCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/comments/SetCommentsCmd.java @@ -15,10 +15,13 @@ */ package ghidra.app.cmd.comments; +import java.util.Objects; + import ghidra.app.util.viewer.field.CommentUtils; import ghidra.framework.cmd.Command; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; +import ghidra.util.exception.AssertException; /** * Command for editing and removing comments at an address. @@ -31,7 +34,6 @@ public class SetCommentsCmd implements Command { private String eolComment; private String plateComment; private String repeatableComment; - private String msg; /** * Construct command for setting all the different types of comments at an @@ -54,66 +56,50 @@ public class SetCommentsCmd implements Command { this.repeatableComment = newRepeatableComment; } - /** - * The name of the edit action. - */ @Override public String getName() { return "Set Comments"; } - /** - * return true if the newValue and oldValue are different - * @param newValue the value that we desire to set - * @param oldValue the existing value - * @return boolean - */ - private boolean commentChanged(String newValue, String oldValue) { - if (newValue == null && oldValue == null) { - return false; - } - if (newValue != null) { - return !newValue.equals(oldValue); - } - return !oldValue.equals(newValue); - } - @Override public boolean applyTo(Program program) { CodeUnit cu = getCodeUnit(program); + if (cu == null) { + return false; + } - if (cu != null) { - Address loc = cu.getAddress(); - if (commentChanged(cu.getComment(CommentType.PRE), preComment)) { - String updatedPreComment = CommentUtils.fixupAnnotations(preComment, program, loc); - updatedPreComment = CommentUtils.sanitize(updatedPreComment); - cu.setComment(CommentType.PRE, updatedPreComment); - } - if (commentChanged(cu.getComment(CommentType.POST), postComment)) { - String updatedPostComment = CommentUtils.fixupAnnotations(postComment, program, loc); - updatedPostComment = CommentUtils.sanitize(updatedPostComment); - cu.setComment(CommentType.POST, updatedPostComment); - } - if (commentChanged(cu.getComment(CommentType.EOL), eolComment)) { - String updatedEOLComment = CommentUtils.fixupAnnotations(eolComment, program, loc); - updatedEOLComment = CommentUtils.sanitize(updatedEOLComment); - cu.setComment(CommentType.EOL, updatedEOLComment); - } - if (commentChanged(cu.getComment(CommentType.PLATE), plateComment)) { - String updatedPlateComment = CommentUtils.fixupAnnotations(plateComment, program, loc); - updatedPlateComment = CommentUtils.sanitize(updatedPlateComment); - cu.setComment(CommentType.PLATE, updatedPlateComment); - } - if (commentChanged(cu.getComment(CommentType.REPEATABLE), repeatableComment)) { - String updatedRepeatableComment = - CommentUtils.fixupAnnotations(repeatableComment, program, loc); - updatedRepeatableComment = CommentUtils.sanitize(updatedRepeatableComment); - cu.setComment(CommentType.REPEATABLE, updatedRepeatableComment); + Address addr = cu.getAddress(); + for (CommentType type : CommentType.values()) { + String newComment = getComment(type); + String oldComment = cu.getComment(type); + if (Objects.equals(oldComment, newComment)) { + continue; } + + String fixedComment = CommentUtils.fixupAnnotations(newComment, program, addr); + fixedComment = CommentUtils.sanitize(fixedComment); + cu.setComment(type, fixedComment); } return true; } + private String getComment(CommentType type) { + switch (type) { + case EOL: + return eolComment; + case PLATE: + return plateComment; + case POST: + return postComment; + case PRE: + return preComment; + case REPEATABLE: + return repeatableComment; + default: + throw new AssertException("Unhandled CommentType"); + } + } + /** * Get the code unit from the program location provider. * @@ -135,7 +121,6 @@ public class SetCommentsCmd implements Command { @Override public String getStatusMsg() { - return msg; + return null; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java index 9c3305911a..0b7658ba64 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java @@ -15,7 +15,6 @@ */ package ghidra.app.util.viewer.field; -import java.awt.event.MouseEvent; import java.util.Objects; import docking.widgets.fieldpanel.field.AttributedString; @@ -24,16 +23,15 @@ import ghidra.app.nav.Navigatable; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; import ghidra.util.bean.field.AnnotatedTextFieldElement; import ghidra.util.classfinder.ExtensionPoint; /** - * NOTE: ALL AnnotatedStringHandler CLASSES MUST END IN "StringHandler". If not, - * the ClassSearcher will not find them. - * * An interface that describes a string that has been annotated, which allows for adding * rendering and functionality to strings. + *

+ * NOTE: ALL AnnotatedStringHandler class names MUST END IN "StringHandler". If not, the + * ClassSearcher will not find them. */ public interface AnnotatedStringHandler extends ExtensionPoint { @@ -45,7 +43,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * @param s string to escape * @return escaped string */ - static String escapeAnnotationPart(String s) { + public static String escapeAnnotationPart(String s) { s = Objects.requireNonNullElse(s, ""); String origStr = s; if (s.indexOf('"') >= 0) { @@ -57,14 +55,6 @@ public interface AnnotatedStringHandler extends ExtensionPoint { return s; } - AnnotatedMouseHandler DUMMY_MOUSE_HANDLER = new AnnotatedMouseHandler() { - @Override - public boolean handleMouseClick(ProgramLocation location, MouseEvent mouseEvent, - ServiceProvider serviceProvider) { - return false; - } - }; - /** * Creates an {@link FieldElement} based upon the give array of Strings. The first String * in the list is expected to be the annotation tag used to create the annotation. At the @@ -81,7 +71,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * @throws AnnotationException if the given text data does not fit the expected format for * the given handler implementation. */ - AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, + public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, Program program) throws AnnotationException; /** @@ -90,44 +80,44 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * * @return the annotation string names that this AnnotatedStringHandler supports. */ - String[] getSupportedAnnotations(); + public String[] getSupportedAnnotations(); /** * A method that is notified when an annotation is clicked. Returns true if this annotation * handles the click; return false if this annotation does not do anything with the click. * - * @param annotationParts The constituent parts of the annotation - * @param sourceNavigatable The location in the program that was clicked. - * @param serviceProvider A service provider for needed services. + * @param text the constituent parts of the annotation + * @param sourceNavigatable the location in the program that was clicked. + * @param serviceProvider the service provider for needed services. * @return true if this annotation handles the click; return false if this annotation does * not do anything with the click. */ - boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable, + public boolean handleMouseClick(String[] text, Navigatable sourceNavigatable, ServiceProvider serviceProvider); /** * Returns the String that represents the GUI presence of this option * @return the String to display in GUI components. */ - String getDisplayString(); + public String getDisplayString(); /** * Returns an example string of how the annotation is used * @return the example of how this is used. */ - String getPrototypeString(); + public String getPrototypeString(); /** * Returns an example string of how the annotation is used - * @param displayText The text that may be wrapped, cannot be null + * @param displayText the text that may be wrapped, will not be null * @return the example of how this is used. */ - default String getPrototypeString(String displayText) { + public default String getPrototypeString(String displayText) { return getPrototypeString(); } - + @Deprecated(forRemoval = true) - default String[] modify(String[] text, Program program) { + public default String[] modify(String[] text, Program program) { return modify(text, program, Address.NO_ADDRESS); } @@ -138,10 +128,10 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * * @param text the array of annotation parts to modify * @param program the program - * @param loc location of the annotation in the program + * @param addr address of the annotation in the program * @return the modified array; null otherwise */ - default String[] modify(String[] text, Program program, Address loc) { + public default String[] modify(String[] text, Program program, Address addr) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java index de4fc5ed3b..d160fb144e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java @@ -50,10 +50,10 @@ public class CommentUtils { * * @param rawCommentText the text to be updated * @param program the program associated with the comment - * @param loc location of comment in program + * @param addr address of comment in program * @return the updated string */ - public static String fixupAnnotations(String rawCommentText, Program program, Address loc) { + public static String fixupAnnotations(String rawCommentText, Program program, Address addr) { if (rawCommentText == null) { return null; @@ -67,7 +67,7 @@ public class CommentUtils { String[] annotationParts = annotation.getAnnotationParts(); AnnotatedStringHandler handler = getAnnotationHandler(annotationParts); - String[] updatedParts = handler.modify(annotationParts, program, loc); + String[] updatedParts = handler.modify(annotationParts, program, addr); if (updatedParts == null) { return annotation; // nothing to change } @@ -164,8 +164,7 @@ public class CommentUtils { text = StringUtilities.convertTabsToSpaces(text); int column = 0; - List parts = - doParseTextIntoParts(text, fixerUpper, program); + List parts = doParseTextIntoParts(text, fixerUpper, program); List fields = new ArrayList<>(); for (CommentPart part : parts) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SymbolAnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SymbolAnnotatedStringHandler.java index fa8dbbfa76..54bdfb59b8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SymbolAnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SymbolAnnotatedStringHandler.java @@ -32,12 +32,13 @@ import ghidra.util.Msg; * An annotated string handler that handles annotations that begin with * {@link #SUPPORTED_ANNOTATIONS}. This class expects one string following the annotation * text that is the address or a symbol name. The display text will be that of the symbol that - * is referred to by the address or symbol name. + * is referred to by the address or symbol name. The symbol name may be the fully-qualified + * namespace path. */ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler { private static final String INVALID_SYMBOL_TEXT = - "@symbol annotation must have a valid " + "symbol name or address"; + "@symbol annotation must have a valid symbol name or address"; private static final String[] SUPPORTED_ANNOTATIONS = { "symbol", "sym" }; @Override @@ -126,7 +127,7 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler { } @Override - public String[] modify(String[] text, Program program, Address loc) { + public String[] modify(String[] text, Program program, Address addr) { if (text.length <= 1) { return null; } @@ -148,6 +149,11 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler { } Address symbolAddress = symbols.get(0).getAddress(); + if (symbolAddress.isVariableAddress()) { + // we can't use variable addresses to locate symbols; there may be other types to ignore + return null; + } + text[1] = symbolAddress.toString(); return text; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableAnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableAnnotatedStringHandler.java index ec6962a133..47e91c0b62 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableAnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableAnnotatedStringHandler.java @@ -15,6 +15,9 @@ */ package ghidra.app.util.viewer.field; +import java.util.Arrays; +import java.util.stream.Collectors; + import docking.widgets.fieldpanel.field.AttributedString; import generic.theme.GThemeDefaults.Colors.Palette; import ghidra.app.nav.Navigatable; @@ -22,122 +25,38 @@ import ghidra.app.services.GoToService; import ghidra.app.util.NamespaceUtils; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.Symbol; +import ghidra.util.Msg; /** - * An annotated string handler that handles annotations that begin with - * {@link #SUPPORTED_ANNOTATIONS}. This class expects one string following the annotation - * text that is the name of a script. + * Provides support for local function variable annotations. + *

+ * This allows users to reference function variables in comments without having to change the + * comment text when the variable is renamed. Users can enter the annotation using the variable's + * name: + *

+ * 	{@variable local_8}
+ *		 		
+ *	 or
+ *		 		
+ *	{@variable coolVariable SomeFunction}
+ * 
+ * The user annotation will be converted to use address information: + *
+ * 	{@variable Stack[0xa] FUN_1234eaea}
+ * 
*/ public class VariableAnnotatedStringHandler implements AnnotatedStringHandler { - private static final String DEFAULT_ANO = "var"; - private static final String HASH_ANO = DEFAULT_ANO + "_hash"; - private static final String[] SUPPORTED_ANNOTATIONS = { "variable", DEFAULT_ANO, HASH_ANO }; - - private static final String INVALID_SYMBOL_TEXT = - "@" + DEFAULT_ANO + " annotation must have form: []"; - - - @Override - public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, - Program program) { - if (program == null) { // this can happen during merge operations - final StringBuilder buffer = new StringBuilder(); - for (String string : text) { - buffer.append(string).append(" "); - } - - return new AttributedString(buffer.toString(), Palette.LIGHT_GRAY, - prototypeString.getFontMetrics(0)); - } - - if (text.length != 3) { - if (text.length == 2) { - throw new AnnotationException("Function symbol is not optional when rendering."); - } - throw new AnnotationException(INVALID_SYMBOL_TEXT); - } - - final Function func = getFunction(program, text[2]); - if (func == null) { - throw new AnnotationException("Could not find function matching \"" + text[2] + "\""); - } - final Variable var = getVariable(func, getFilterGenerator(text[0]).apply(text[1])); - if (var == null) { - throw new AnnotationException("Could not find variable in function \"" + - func.getName() + "\" matching \"" + text[1] + "\"."); - } - - return new AttributedString(var.getName(), prototypeString.getColor(0), - prototypeString.getFontMetrics(0), true, prototypeString.getColor(0)); - } - - @Override - public String[] modify(String[] text, Program program, Address loc) { - if (program == null) { // this can happen during merge operations - return null; - } - - Function func = null; - switch (text.length) { - case 3: - func = getFunction(program, text[2]); - break; - case 2: - func = program.getFunctionManager().getFunctionContaining(loc); - break; - default: - return null; - } - - if (func == null) { - return null; - } - - final Variable var = getVariable(func, getFilterGenerator(text[0]).apply(text[1])); - if (var == null) { - return null; - } - - return new String[] { - HASH_ANO, - Integer.toUnsignedString(var.hashCode(), 16), - func.getEntryPoint().toString() - }; - } + private static final String[] SUPPORTED_ANNOTATIONS = { "variable", "var" }; @Override public String[] getSupportedAnnotations() { return SUPPORTED_ANNOTATIONS; } - @Override - public boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable, - ServiceProvider serviceProvider) { - final Program program = sourceNavigatable.getProgram(); - - if (annotationParts.length != 3) { - return false; - } - final Function func = getFunction(program, annotationParts[2]); - if (func == null) { - return false; - } - final Variable var = getVariable(func, getFilterGenerator(annotationParts[0]).apply(annotationParts[1])); - if (var == null) { - return false; - } - Symbol sym = var.getSymbol(); - if (sym == null) { - sym = func.getSymbol(); - } - - final GoToService goToService = serviceProvider.getService(GoToService.class); - return goToService.goTo(sym.getProgramLocation()); - } - @Override public String getDisplayString() { return "Variable"; @@ -145,40 +64,71 @@ public class VariableAnnotatedStringHandler implements AnnotatedStringHandler { @Override public String getPrototypeString() { - return "{@" + DEFAULT_ANO + " var_sym [func_sym]}"; + return "{@variable variable_name}"; } @Override public String getPrototypeString(String displayText) { - return "{@" + DEFAULT_ANO + " " + displayText.trim() + "}"; + return "{@variable " + displayText.trim() + "}"; } - private static Function getFunction(final Program program, final String name) { - final FunctionManager func_manager = program.getFunctionManager(); + @Override + public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, + Program program) throws AnnotationException { - for (Symbol sym : NamespaceUtils.getSymbols(name, program)) { - final Function func = func_manager.getFunctionAt(sym.getAddress()); - if (func != null) { - return func; + /* + We expect to be handed annotation text that was updated via a previous call to modify(). + The annotation will be of the form {@variable variable_address function_address} + */ + if (text.length != 3) { + throw new AnnotationException( + "@variable annotation must have a variable address and a function name"); + } + + if (program == null) { // this can happen during merge operations + return createPlaceholderString(prototypeString, text); + } + + String functionName = text[2]; + Function function = getFunction(program, functionName); + if (function == null) { + throw new AnnotationException("Could not find function \"" + functionName + "\""); + } + + String address = text[1]; + Variable var = getVariable(function, address); + if (var == null) { + throw new AnnotationException("Could not find variable in function \"" + + functionName + "\" matching \"" + text[1] + "\"."); + } + + return new AttributedString(var.getName(), prototypeString.getColor(0), + prototypeString.getFontMetrics(0), true, prototypeString.getColor(0)); + } + + private AttributedString createPlaceholderString(AttributedString prototypeString, + String[] text) { + String joined = Arrays.stream(text).collect(Collectors.joining(" ")); + return new AttributedString(joined, Palette.LIGHT_GRAY, + prototypeString.getFontMetrics(0)); + } + + private static Function getFunction(Program program, String name) { + FunctionManager manager = program.getFunctionManager(); + for (Symbol s : NamespaceUtils.getSymbols(name, program)) { + Address addr = s.getAddress(); + Function function = manager.getFunctionAt(addr); + if (function != null) { + return function; } } // if we get here, then see if the value is an address - final Address addr = program.getAddressFactory().getAddress(name); + Address addr = program.getAddressFactory().getAddress(name); if (addr != null) { - final Function func = func_manager.getFunctionAt(addr); - if (func != null) { - return func; - } - } - - return null; - } - - private static Variable getVariable(final Function func, JFunction name_filter) { - for (Variable var : func.getAllVariables()) { - if (name_filter.apply(var)) { - return var; + Function function = manager.getFunctionAt(addr); + if (function != null) { + return function; } } @@ -186,32 +136,147 @@ public class VariableAnnotatedStringHandler implements AnnotatedStringHandler { } /** - * Get the correct filter generator for parsing the local variable. - * - * @param name the name of the annotation - * @return the filter generator + * Update the annotation to convert names to addresses. + * @param text the array of annotation parts to modify + * @param program the program + * @param addr address of the annotation in the program + * @return the modified array; null otherwise */ - private static JFunction> getFilterGenerator(final String name) { - switch (name) { - case HASH_ANO: - return VariableAnnotatedStringHandler::hashFilterGen; - default: - return VariableAnnotatedStringHandler::nameFilterGen; + @Override + public String[] modify(String[] text, Program program, Address addr) { + if (program == null) { // this can happen during merge operations + return null; + } + + Function function = null; + switch (text.length) { + case 3: + function = getFunction(program, text[2]); + break; + case 2: + function = program.getFunctionManager().getFunctionContaining(addr); + break; + default: + return null; + } + + if (function == null) { + return null; + } + + String value = text[1]; // value is a variable name or address + VariableMatcher matcher = createVariableMatcher(program, value); + Variable var = findVariable(function, matcher); + if (var == null) { + return null; + } + + return new String[] { + "variable", + var.getMinAddress().toString(), + function.getEntryPoint().toString() + }; + } + + private VariableMatcher createVariableMatcher(Program p, String value) { + + Address addr = toAddress(p, value); + if (addr != null) { + return new AddressMatcher(addr); + } + + return new NameMatcher(value); + } + + @Override + public boolean handleMouseClick(String[] text, Navigatable sourceNavigatable, + ServiceProvider serviceProvider) { + + /* + We expect to be handed annotation text that was updated via a previous call to modify(). + The annotation will be of the form {@variable variable_address function_address} + */ + if (text.length != 3) { + return false; + } + + Program program = sourceNavigatable.getProgram(); + Function function = getFunction(program, text[2]); + if (function == null) { + return false; + } + + String address = text[1]; + Variable var = getVariable(function, address); + if (var == null) { + return false; + } + + Symbol symbol = var.getSymbol(); + if (symbol == null) { + symbol = function.getSymbol(); + } + + GoToService goToService = serviceProvider.getService(GoToService.class); + if (goToService == null) { + Msg.debug(this, "GoToService not installed"); + return false; + } + + return goToService.goTo(symbol.getProgramLocation()); + } + + private Variable getVariable(Function function, String addressString) { + Program p = function.getProgram(); + Address addr = toAddress(p, addressString); + VariableMatcher matcher = new AddressMatcher(addr); + return findVariable(function, matcher); + } + + private static Variable findVariable(Function function, VariableMatcher matcher) { + for (Variable var : function.getAllVariables()) { + if (matcher.matches(var)) { + return var; + } + } + + return null; + } + + private static Address toAddress(Program p, String s) { + AddressFactory af = p.getAddressFactory(); + return af.getAddress(s); + } + + private static interface VariableMatcher { + public boolean matches(Variable v); + } + + private static class NameMatcher implements VariableMatcher { + + private String name; + + NameMatcher(String name) { + this.name = name; + } + + @Override + public boolean matches(Variable v) { + return v.getName().equals(name); } } - private static JFunction hashFilterGen(String s) { - try { - final int hash = Integer.parseUnsignedInt(s, 16); - return (x) -> x.hashCode() == hash; - } catch (NumberFormatException e) { - return (x) -> false; + private static class AddressMatcher implements VariableMatcher { + + private Address addr; + + AddressMatcher(Address addr) { + this.addr = addr; + } + + @Override + public boolean matches(Variable v) { + return v.getMinAddress().equals(addr); } } - - private static JFunction nameFilterGen(String s) { - return (x) -> x.getName().equals(s); - } - - private interface JFunction extends java.util.function.Function {} } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java index 08aeee6e0b..0d3c6d166b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java @@ -15,7 +15,7 @@ */ package ghidra.app.util.viewer.field; -import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; @@ -45,6 +45,7 @@ import ghidra.framework.store.FileSystem; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; import ghidra.program.model.data.UnsignedIntegerDataType; import ghidra.program.model.data.VoidDataType; import ghidra.program.model.listing.Function; @@ -64,7 +65,6 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest { private static final String OTHER_PROGRAM_NAME = "program2"; private Program program; - private Function test_func; @Before public void setUp() throws Exception { @@ -87,8 +87,9 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest { builder.createLabel("1001018", "mySym{0}"); // symbol with braces builder.createLabel("1001022", "mySym\\{0\\}"); // symbol with braces escaped - test_func = builder.createEmptyFunction("test_func", "1002000", 0x10, VoidDataType.dataType); - builder.createLocalVariable(test_func, "test_var", UnsignedIntegerDataType.dataType, 10); + Function myFunction = + builder.createEmptyFunction("MyFunction", "1002000", 0x10, VoidDataType.dataType); + builder.createLocalVariable(myFunction, "myVariable", UnsignedIntegerDataType.dataType, 10); return builder.getProgram(); } @@ -742,44 +743,48 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testVariableAnnotation_Basic() { - String rawComment = "{@var test_var test_func}"; + String rawComment = "{@variable Stack[0xa] MyFunction}"; String display = CommentUtils.getDisplayString(rawComment, program); - assertEquals("test_var", display); + assertEquals("myVariable", display); } @Test public void testVariableAnnotation_BasicModify() { - String rawComment = "{@var test_var test_func}"; + String rawComment = "{@variable myVariable MyFunction}"; String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS); - assertEquals("{@var_hash a016221 01002000}", display); + assertEquals("{@variable Stack[0xa] 01002000}", display); } - + @Test public void testVariableAnnotation_BasicModify_NoFunction() { - String rawComment = "{@var test_var}"; - String display = CommentUtils.fixupAnnotations(rawComment, program, test_func.getEntryPoint()); - assertEquals("{@var_hash a016221 01002000}", display); + String rawComment = "{@variable myVariable}"; + String functionAddress = "01002000"; + Address entryPoint = addr(functionAddress); + String display = CommentUtils.fixupAnnotations(rawComment, program, entryPoint); + assertEquals("{@variable Stack[0xa] 01002000}", display); } @Test public void testLocalAnnotation_UserMarked() { - String rawComment = "{@var_hash a016221 test_func}"; + String rawComment = "{@variable Stack[0xa] MyFunction}"; String display = CommentUtils.getDisplayString(rawComment, program); - assertEquals("test_var", display); + assertEquals("myVariable", display); } @Test public void testLocalAnnotation_UserMarkedModify() { - String rawComment = "{@var_hash a016221 test_func}"; + String rawComment = "{@variable Stack[0xa] MyFunction}"; String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS); - assertEquals("{@var_hash a016221 01002000}", display); + assertEquals("{@variable Stack[0xa] 01002000}", display); } @Test public void testLocalAnnotation_UserMarkedModify_NoFunction() { - String rawComment = "{@var_hash a016221}"; - String display = CommentUtils.fixupAnnotations(rawComment, program, test_func.getEntryPoint()); - assertEquals("{@var_hash a016221 01002000}", display); + String rawComment = "{@variable Stack[0xa]}"; + String functionAddress = "01002000"; + Address entryPoint = addr(functionAddress); + String display = CommentUtils.fixupAnnotations(rawComment, program, entryPoint); + assertEquals("{@variable Stack[0xa] 01002000}", display); } @Test @@ -966,6 +971,10 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest { waitForSwing(); // let post-dialog processing happen } + private Address addr(String offset) { + AddressFactory af = program.getAddressFactory(); + return af.getAddress(offset); + } //================================================================================================== // Fake/Spy Classes //================================================================================================== diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/VariableDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/VariableDB.java index 5de1fc04b7..018c0b0217 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/VariableDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/VariableDB.java @@ -16,8 +16,7 @@ package ghidra.program.database.function; import java.util.List; - -import org.apache.commons.lang3.StringUtils; +import java.util.Objects; import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.database.symbol.SymbolDB; @@ -353,14 +352,13 @@ public abstract class VariableDB implements Variable { } Variable otherVar = (Variable) obj; - if (!isEquivalent(otherVar)) { return false; } - if (!StringUtils.equals(getName(), otherVar.getName())) { + if (!Objects.equals(getName(), otherVar.getName())) { return false; } - return StringUtils.equals(getComment(), otherVar.getComment()); + return Objects.equals(getComment(), otherVar.getComment()); } @Override