Merge remote-tracking branch

'origin/GP-6561_dragonmacher_PR-8993_hay-mon_local_hndl' (Closes #8993)
This commit is contained in:
Ryan Kurtz
2026-05-17 19:11:16 -04:00
8 changed files with 485 additions and 105 deletions
@@ -104,11 +104,11 @@
Notes:
<ul style="margin-left: 10px;">
<li>
If you provide a symbol name and not an address, then you will need to
fully-qualify the symbol name if it is not in the global namespace (e.g.,
FunctionFoo::param1 to link to a specific function parameter).
</li>
<li>
If you provide a symbol name and not an address, then you will need to
fully-qualify the symbol name if it is not in the global namespace (e.g.,
FunctionFoo::param1 to link to a specific function parameter).
</li>
<li>
If you specify a symbol name instead of an address, then the text of the comment
will be changed to use the address of that symbol.
@@ -285,6 +285,51 @@
</TD>
</TR>
<TR>
<TD valign="top" width="5%">Variable<BR>
</TD>
<TD valign="top" width="15%">Displays the text of the given variable or parameter
for a given function as a hyperlink.<BR>
<BR>
<BR>
Notes:
<ul style="margin-left: 10px;">
<li>
This annotation will modify itself to use the variable and function
address to survive renaming of the variable and the function.
</li>
</ul>
</TD>
<TD valign="top" width="12%">
<OL style="margin-left: 15px;">
<LI>variable/parameter name (@variable) or hash identifier</LI>
<LI>[function name or address]<BR>
</LI>
</OL>
</TD>
<TD valign="top" width="10%">
<UL style="margin-left: 10px;">
<LI>@variable</LI>
<LI>@var</LI>
</UL>
</TD>
<TD valign="top" width="25%">
<UL style="margin-left: 10px;">
<LI>{@variable "i"}</LI>
<LI>{@var "argv" "main"}</LI>
</LI>
</UL>
</TD>
</TR>
<TR>
<TD valign="top" width="5%"><I>Discovered Annotations</I><BR>
</TD>
@@ -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<Program> {
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,65 +56,50 @@ public class SetCommentsCmd implements Command<Program> {
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) {
if (commentChanged(cu.getComment(CommentType.PRE), preComment)) {
String updatedPreComment = CommentUtils.fixupAnnotations(preComment, program);
updatedPreComment = CommentUtils.sanitize(updatedPreComment);
cu.setComment(CommentType.PRE, updatedPreComment);
}
if (commentChanged(cu.getComment(CommentType.POST), postComment)) {
String updatedPostComment = CommentUtils.fixupAnnotations(postComment, program);
updatedPostComment = CommentUtils.sanitize(updatedPostComment);
cu.setComment(CommentType.POST, updatedPostComment);
}
if (commentChanged(cu.getComment(CommentType.EOL), eolComment)) {
String updatedEOLComment = CommentUtils.fixupAnnotations(eolComment, program);
updatedEOLComment = CommentUtils.sanitize(updatedEOLComment);
cu.setComment(CommentType.EOL, updatedEOLComment);
}
if (commentChanged(cu.getComment(CommentType.PLATE), plateComment)) {
String updatedPlateComment = CommentUtils.fixupAnnotations(plateComment, program);
updatedPlateComment = CommentUtils.sanitize(updatedPlateComment);
cu.setComment(CommentType.PLATE, updatedPlateComment);
}
if (commentChanged(cu.getComment(CommentType.REPEATABLE), repeatableComment)) {
String updatedRepeatableComment =
CommentUtils.fixupAnnotations(repeatableComment, program);
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.
*
@@ -134,7 +121,6 @@ public class SetCommentsCmd implements Command<Program> {
@Override
public String getStatusMsg() {
return msg;
return null;
}
}
@@ -15,24 +15,23 @@
*/
package ghidra.app.util.viewer.field;
import java.awt.event.MouseEvent;
import java.util.Objects;
import docking.widgets.fieldpanel.field.AttributedString;
import docking.widgets.fieldpanel.field.FieldElement;
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.
* <p>
* NOTE: ALL AnnotatedStringHandler class names MUST END IN "StringHandler". If not, the
* ClassSearcher will not find them.
*/
public interface AnnotatedStringHandler extends ExtensionPoint {
@@ -44,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) {
@@ -56,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
@@ -80,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;
/**
@@ -89,42 +80,47 @@ 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)
public default String[] modify(String[] text, Program program) {
return modify(text, program, Address.NO_ADDRESS);
}
/**
* Returns an array with modifications by the annotation; null otherwise. This method will be
* called by the framework when comments are created, before they are applied. This allows the
@@ -132,9 +128,10 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
*
* @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
*/
default String[] modify(String[] text, Program program) {
public default String[] modify(String[] text, Program program, Address addr) {
return null;
}
@@ -50,9 +50,10 @@ public class CommentUtils {
*
* @param rawCommentText the text to be updated
* @param program the program associated with the comment
* @param addr address of comment in program
* @return the updated string
*/
public static String fixupAnnotations(String rawCommentText, Program program) {
public static String fixupAnnotations(String rawCommentText, Program program, Address addr) {
if (rawCommentText == null) {
return null;
@@ -66,7 +67,7 @@ public class CommentUtils {
String[] annotationParts = annotation.getAnnotationParts();
AnnotatedStringHandler handler = getAnnotationHandler(annotationParts);
String[] updatedParts = handler.modify(annotationParts, program);
String[] updatedParts = handler.modify(annotationParts, program, addr);
if (updatedParts == null) {
return annotation; // nothing to change
}
@@ -163,8 +164,7 @@ public class CommentUtils {
text = StringUtilities.convertTabsToSpaces(text);
int column = 0;
List<CommentPart> parts =
doParseTextIntoParts(text, fixerUpper, program);
List<CommentPart> parts = doParseTextIntoParts(text, fixerUpper, program);
List<FieldElement> fields = new ArrayList<>();
for (CommentPart part : parts) {
@@ -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) {
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;
@@ -0,0 +1,290 @@
/* ###
* 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.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;
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;
/**
* Provides support for local function variable annotations.
* <p>
* 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:
* <pre>
* {@variable local_8}
*
* or
*
* {@variable coolVariable SomeFunction}
* </pre>
* The user annotation will be converted to use address information:
* <pre>
* {@variable Stack[0xa] FUN_1234eaea}
* </pre>
*/
public class VariableAnnotatedStringHandler implements AnnotatedStringHandler {
private static final String[] SUPPORTED_ANNOTATIONS = { "variable", "var" };
@Override
public String[] getSupportedAnnotations() {
return SUPPORTED_ANNOTATIONS;
}
@Override
public String getDisplayString() {
return "Variable";
}
@Override
public String getPrototypeString() {
return "{@variable variable_name}";
}
@Override
public String getPrototypeString(String displayText) {
return "{@variable " + displayText.trim() + "}";
}
@Override
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
Program program) throws AnnotationException {
/*
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 entry = text[2];
Function function = getFunctionAt(program, entry);
if (function == null) {
throw new AnnotationException("Could not find function \"" + entry + "\"");
}
String address = text[1];
Variable var = getVariable(function, address);
if (var == null) {
String functionName = function.getName();
throw new AnnotationException(
"Could not find variable at address %s in function %s".formatted(functionName,
address));
}
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 Function getFunction(Program program, String value) {
FunctionManager manager = program.getFunctionManager();
for (Symbol s : NamespaceUtils.getSymbols(value, 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
return getFunctionAt(program, value);
}
private Function getFunctionAt(Program program, String address) {
FunctionManager manager = program.getFunctionManager();
AddressFactory af = program.getAddressFactory();
Address addr = af.getAddress(address);
if (addr != null) {
Function function = manager.getFunctionAt(addr);
if (function != null) {
return function;
}
}
return null;
}
/**
* 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
*/
@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 class AddressMatcher implements VariableMatcher {
private Address addr;
AddressMatcher(Address addr) {
this.addr = addr;
}
@Override
public boolean matches(Variable v) {
return v.getMinAddress().equals(addr);
}
}
}
@@ -45,6 +45,10 @@ 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;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.*;
@@ -83,51 +87,55 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
builder.createLabel("1001018", "mySym{0}"); // symbol with braces
builder.createLabel("1001022", "mySym\\{0\\}"); // symbol with braces escaped
Function myFunction =
builder.createEmptyFunction("MyFunction", "1002000", 0x10, VoidDataType.dataType);
builder.createLocalVariable(myFunction, "myVariable", UnsignedIntegerDataType.dataType, 10);
return builder.getProgram();
}
@Test
public void testSymbolAnnotationWithAddress() {
String rawComment = "This is a symbol {@sym 01001014} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals(rawComment, fixed);
// with display string
rawComment = "This is a symbol {@sym 01001014 bob} annotation.";
fixed = CommentUtils.fixupAnnotations(rawComment, program);
fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals(rawComment, fixed);
}
@Test
public void testSymbolAnnotationWithInvalidAddress() {
String rawComment = "This is a symbol {@sym 999999} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals(rawComment, fixed);
}
@Test
public void testSymbolAnnotationWithSymbol() {
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("This is a symbol {@sym 01003d2c} annotation.", fixed);
// with display string
rawComment = "This is a symbol {@sym LAB_01003d2c displayText} annotation.";
fixed = CommentUtils.fixupAnnotations(rawComment, program);
fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("This is a symbol {@sym 01003d2c displayText} annotation.", fixed);
}
@Test
public void testSymbolAnnotationWithInvalidSymbol() {
String rawComment = "This is a symbol {@sym CocoPebbles} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("This is a symbol {@sym CocoPebbles} annotation.", fixed);
}
@Test
public void testNoAnnotation() {
String rawComment = "This is no symbol annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals(rawComment, fixed);
}
@@ -135,7 +143,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testMixedAnnotationNoSymbolAnnotation() {
String rawComment = "This is a symbol {@url www.noplace.com} annotation " +
"with a {@program notepad} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals(rawComment, fixed);
}
@@ -143,7 +151,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testMixedAnnotationWithSymbolAnnotation() {
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " +
"with a {@program notepad} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("This is a symbol {@sym 01003d2c} annotation " +
"with a {@program notepad} annotation.", fixed);
}
@@ -151,21 +159,21 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testSymbolAnnotationAtBeginningOfComment() {
String rawComment = "{@sym LAB_01003d2c} annotation at the beginning.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("{@sym 01003d2c} annotation at the beginning.", fixed);
}
@Test
public void testSymbolAnnotation_BackToBack() {
String rawComment = "Test {@sym LAB_01003d2c}{@sym LAB_01003d2c} end.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("Test {@sym 01003d2c}{@sym 01003d2c} end.", fixed);
}
@Test
public void testSymbolAnnotationAtEndOfComment() {
String rawComment = "Annotation at the end {@sym LAB_01003d2c}";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("Annotation at the end {@sym 01003d2c}", fixed);
}
@@ -173,7 +181,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testSymbolAnnotationAtBeginningAndEndOfComment() {
String rawComment =
"{@sym LAB_01003d2c} annotation at the beginning and end {@sym LAB_01003d5b}";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("{@sym 01003d2c} annotation at the " + "beginning and end {@sym 01003d5b}",
fixed);
}
@@ -183,7 +191,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
String rawComment =
"{@sym LAB_01003d2c} annotation at the beginning, middle {@sym LAB_01003d28} and " +
"end {@sym LAB_01003d5b}";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("{@sym 01003d2c} annotation at the beginning, middle {@sym 01003d28} and " +
"end {@sym 01003d5b}", fixed);
}
@@ -192,7 +200,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testSymbolAnnotationWithValidAndInvalidSymbol() {
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " +
"with a {@sym FruityPebbles} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("This is a symbol {@sym 01003d2c} annotation " +
"with a {@sym FruityPebbles} annotation.", fixed);
}
@@ -733,6 +741,52 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
// Navigation performed by ProgramManager not tested due to use of spyServiceProvider
}
@Test
public void testVariableAnnotation_Basic() {
String rawComment = "{@variable Stack[0xa] MyFunction}";
String display = CommentUtils.getDisplayString(rawComment, program);
assertEquals("myVariable", display);
}
@Test
public void testVariableAnnotation_BasicModify() {
String rawComment = "{@variable myVariable MyFunction}";
String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("{@variable Stack[0xa] 01002000}", display);
}
@Test
public void testVariableAnnotation_BasicModify_NoFunction() {
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 = "{@variable Stack[0xa] MyFunction}";
String display = CommentUtils.getDisplayString(rawComment, program);
assertEquals("myVariable", display);
}
@Test
public void testLocalAnnotation_UserMarkedModify() {
String rawComment = "{@variable Stack[0xa] MyFunction}";
String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals("{@variable Stack[0xa] 01002000}", display);
}
@Test
public void testLocalAnnotation_UserMarkedModify_NoFunction() {
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
public void testUnknownAnnotation() {
String rawComment = "This is a symbol {@syyyybol bob} annotation";
@@ -917,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
//==================================================================================================
@@ -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