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

View File

@@ -104,11 +104,11 @@
Notes: Notes:
<ul style="margin-left: 10px;"> <ul style="margin-left: 10px;">
<li> <li>
If you provide a symbol name and not an address, then you will need to 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., fully-qualify the symbol name if it is not in the global namespace (e.g.,
FunctionFoo::param1 to link to a specific function parameter). FunctionFoo::param1 to link to a specific function parameter).
</li> </li>
<li> <li>
If you specify a symbol name instead of an address, then the text of the comment 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. will be changed to use the address of that symbol.
@@ -285,6 +285,51 @@
</TD> </TD>
</TR> </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> <TR>
<TD valign="top" width="5%"><I>Discovered Annotations</I><BR> <TD valign="top" width="5%"><I>Discovered Annotations</I><BR>
</TD> </TD>

View File

@@ -15,10 +15,13 @@
*/ */
package ghidra.app.cmd.comments; package ghidra.app.cmd.comments;
import java.util.Objects;
import ghidra.app.util.viewer.field.CommentUtils; import ghidra.app.util.viewer.field.CommentUtils;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.util.exception.AssertException;
/** /**
* Command for editing and removing comments at an address. * Command for editing and removing comments at an address.
@@ -31,7 +34,6 @@ public class SetCommentsCmd implements Command<Program> {
private String eolComment; private String eolComment;
private String plateComment; private String plateComment;
private String repeatableComment; private String repeatableComment;
private String msg;
/** /**
* Construct command for setting all the different types of comments at an * 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; this.repeatableComment = newRepeatableComment;
} }
/**
* The name of the edit action.
*/
@Override @Override
public String getName() { public String getName() {
return "Set Comments"; 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 @Override
public boolean applyTo(Program program) { public boolean applyTo(Program program) {
CodeUnit cu = getCodeUnit(program); CodeUnit cu = getCodeUnit(program);
if (cu == null) {
return false;
}
if (cu != null) { Address addr = cu.getAddress();
if (commentChanged(cu.getComment(CommentType.PRE), preComment)) { for (CommentType type : CommentType.values()) {
String updatedPreComment = CommentUtils.fixupAnnotations(preComment, program); String newComment = getComment(type);
updatedPreComment = CommentUtils.sanitize(updatedPreComment); String oldComment = cu.getComment(type);
cu.setComment(CommentType.PRE, updatedPreComment); if (Objects.equals(oldComment, newComment)) {
} continue;
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);
} }
String fixedComment = CommentUtils.fixupAnnotations(newComment, program, addr);
fixedComment = CommentUtils.sanitize(fixedComment);
cu.setComment(type, fixedComment);
} }
return true; 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. * Get the code unit from the program location provider.
* *
@@ -134,7 +121,6 @@ public class SetCommentsCmd implements Command<Program> {
@Override @Override
public String getStatusMsg() { public String getStatusMsg() {
return msg; return null;
} }
} }

View File

@@ -15,24 +15,23 @@
*/ */
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.awt.event.MouseEvent;
import java.util.Objects; import java.util.Objects;
import docking.widgets.fieldpanel.field.AttributedString; import docking.widgets.fieldpanel.field.AttributedString;
import docking.widgets.fieldpanel.field.FieldElement; import docking.widgets.fieldpanel.field.FieldElement;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.bean.field.AnnotatedTextFieldElement; import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.classfinder.ExtensionPoint; 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 * An interface that describes a string that has been annotated, which allows for adding
* rendering and functionality to strings. * 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 { public interface AnnotatedStringHandler extends ExtensionPoint {
@@ -44,7 +43,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
* @param s string to escape * @param s string to escape
* @return escaped string * @return escaped string
*/ */
static String escapeAnnotationPart(String s) { public static String escapeAnnotationPart(String s) {
s = Objects.requireNonNullElse(s, ""); s = Objects.requireNonNullElse(s, "");
String origStr = s; String origStr = s;
if (s.indexOf('"') >= 0) { if (s.indexOf('"') >= 0) {
@@ -56,14 +55,6 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
return s; 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 * 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 * 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 * @throws AnnotationException if the given text data does not fit the expected format for
* the given handler implementation. * the given handler implementation.
*/ */
AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
Program program) throws AnnotationException; Program program) throws AnnotationException;
/** /**
@@ -89,42 +80,47 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
* *
* @return the annotation string names that this AnnotatedStringHandler supports. * @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 * 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. * handles the click; return false if this annotation does not do anything with the click.
* *
* @param annotationParts The constituent parts of the annotation * @param text the constituent parts of the annotation
* @param sourceNavigatable The location in the program that was clicked. * @param sourceNavigatable the location in the program that was clicked.
* @param serviceProvider A service provider for needed services. * @param serviceProvider the service provider for needed services.
* @return true if this annotation handles the click; return false if this annotation does * @return true if this annotation handles the click; return false if this annotation does
* not do anything with the click. * not do anything with the click.
*/ */
boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable, public boolean handleMouseClick(String[] text, Navigatable sourceNavigatable,
ServiceProvider serviceProvider); ServiceProvider serviceProvider);
/** /**
* Returns the String that represents the GUI presence of this option * Returns the String that represents the GUI presence of this option
* @return the String to display in GUI components. * @return the String to display in GUI components.
*/ */
String getDisplayString(); public String getDisplayString();
/** /**
* Returns an example string of how the annotation is used * Returns an example string of how the annotation is used
* @return the example of how this 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 * 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. * @return the example of how this is used.
*/ */
default String getPrototypeString(String displayText) { public default String getPrototypeString(String displayText) {
return getPrototypeString(); 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 * 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 * 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 text the array of annotation parts to modify
* @param program the program * @param program the program
* @param addr address of the annotation in the program
* @return the modified array; null otherwise * @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; return null;
} }

View File

@@ -50,9 +50,10 @@ public class CommentUtils {
* *
* @param rawCommentText the text to be updated * @param rawCommentText the text to be updated
* @param program the program associated with the comment * @param program the program associated with the comment
* @param addr address of comment in program
* @return the updated string * @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) { if (rawCommentText == null) {
return null; return null;
@@ -66,7 +67,7 @@ public class CommentUtils {
String[] annotationParts = annotation.getAnnotationParts(); String[] annotationParts = annotation.getAnnotationParts();
AnnotatedStringHandler handler = getAnnotationHandler(annotationParts); AnnotatedStringHandler handler = getAnnotationHandler(annotationParts);
String[] updatedParts = handler.modify(annotationParts, program); String[] updatedParts = handler.modify(annotationParts, program, addr);
if (updatedParts == null) { if (updatedParts == null) {
return annotation; // nothing to change return annotation; // nothing to change
} }
@@ -163,8 +164,7 @@ public class CommentUtils {
text = StringUtilities.convertTabsToSpaces(text); text = StringUtilities.convertTabsToSpaces(text);
int column = 0; int column = 0;
List<CommentPart> parts = List<CommentPart> parts = doParseTextIntoParts(text, fixerUpper, program);
doParseTextIntoParts(text, fixerUpper, program);
List<FieldElement> fields = new ArrayList<>(); List<FieldElement> fields = new ArrayList<>();
for (CommentPart part : parts) { for (CommentPart part : parts) {

View File

@@ -32,12 +32,13 @@ import ghidra.util.Msg;
* An annotated string handler that handles annotations that begin with * An annotated string handler that handles annotations that begin with
* {@link #SUPPORTED_ANNOTATIONS}. This class expects one string following the annotation * {@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 * 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 { public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
private static final String INVALID_SYMBOL_TEXT = 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" }; private static final String[] SUPPORTED_ANNOTATIONS = { "symbol", "sym" };
@Override @Override
@@ -126,7 +127,7 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
} }
@Override @Override
public String[] modify(String[] text, Program program) { public String[] modify(String[] text, Program program, Address addr) {
if (text.length <= 1) { if (text.length <= 1) {
return null; return null;
} }
@@ -148,6 +149,11 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
} }
Address symbolAddress = symbols.get(0).getAddress(); 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(); text[1] = symbolAddress.toString();
return text; return text;

View File

@@ -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);
}
}
}

View File

@@ -45,6 +45,10 @@ import ghidra.framework.store.FileSystem;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address; 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.listing.Program;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
@@ -83,51 +87,55 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
builder.createLabel("1001018", "mySym{0}"); // symbol with braces builder.createLabel("1001018", "mySym{0}"); // symbol with braces
builder.createLabel("1001022", "mySym\\{0\\}"); // symbol with braces escaped 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(); return builder.getProgram();
} }
@Test @Test
public void testSymbolAnnotationWithAddress() { public void testSymbolAnnotationWithAddress() {
String rawComment = "This is a symbol {@sym 01001014} annotation."; 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); assertEquals(rawComment, fixed);
// with display string // with display string
rawComment = "This is a symbol {@sym 01001014 bob} annotation."; 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); assertEquals(rawComment, fixed);
} }
@Test @Test
public void testSymbolAnnotationWithInvalidAddress() { public void testSymbolAnnotationWithInvalidAddress() {
String rawComment = "This is a symbol {@sym 999999} annotation."; 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); assertEquals(rawComment, fixed);
} }
@Test @Test
public void testSymbolAnnotationWithSymbol() { public void testSymbolAnnotationWithSymbol() {
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation."; 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); assertEquals("This is a symbol {@sym 01003d2c} annotation.", fixed);
// with display string // with display string
rawComment = "This is a symbol {@sym LAB_01003d2c displayText} annotation."; 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); assertEquals("This is a symbol {@sym 01003d2c displayText} annotation.", fixed);
} }
@Test @Test
public void testSymbolAnnotationWithInvalidSymbol() { public void testSymbolAnnotationWithInvalidSymbol() {
String rawComment = "This is a symbol {@sym CocoPebbles} annotation."; 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); assertEquals("This is a symbol {@sym CocoPebbles} annotation.", fixed);
} }
@Test @Test
public void testNoAnnotation() { public void testNoAnnotation() {
String rawComment = "This is no symbol annotation."; 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); assertEquals(rawComment, fixed);
} }
@@ -135,7 +143,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testMixedAnnotationNoSymbolAnnotation() { public void testMixedAnnotationNoSymbolAnnotation() {
String rawComment = "This is a symbol {@url www.noplace.com} annotation " + String rawComment = "This is a symbol {@url www.noplace.com} annotation " +
"with a {@program notepad} annotation."; "with a {@program notepad} annotation.";
String fixed = CommentUtils.fixupAnnotations(rawComment, program); String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
assertEquals(rawComment, fixed); assertEquals(rawComment, fixed);
} }
@@ -143,7 +151,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testMixedAnnotationWithSymbolAnnotation() { public void testMixedAnnotationWithSymbolAnnotation() {
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " + String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " +
"with a {@program notepad} 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 " + assertEquals("This is a symbol {@sym 01003d2c} annotation " +
"with a {@program notepad} annotation.", fixed); "with a {@program notepad} annotation.", fixed);
} }
@@ -151,21 +159,21 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testSymbolAnnotationAtBeginningOfComment() { public void testSymbolAnnotationAtBeginningOfComment() {
String rawComment = "{@sym LAB_01003d2c} annotation at the beginning."; 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); assertEquals("{@sym 01003d2c} annotation at the beginning.", fixed);
} }
@Test @Test
public void testSymbolAnnotation_BackToBack() { public void testSymbolAnnotation_BackToBack() {
String rawComment = "Test {@sym LAB_01003d2c}{@sym LAB_01003d2c} end."; 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); assertEquals("Test {@sym 01003d2c}{@sym 01003d2c} end.", fixed);
} }
@Test @Test
public void testSymbolAnnotationAtEndOfComment() { public void testSymbolAnnotationAtEndOfComment() {
String rawComment = "Annotation at the end {@sym LAB_01003d2c}"; 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); assertEquals("Annotation at the end {@sym 01003d2c}", fixed);
} }
@@ -173,7 +181,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testSymbolAnnotationAtBeginningAndEndOfComment() { public void testSymbolAnnotationAtBeginningAndEndOfComment() {
String rawComment = String rawComment =
"{@sym LAB_01003d2c} annotation at the beginning and end {@sym LAB_01003d5b}"; "{@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}", assertEquals("{@sym 01003d2c} annotation at the " + "beginning and end {@sym 01003d5b}",
fixed); fixed);
} }
@@ -183,7 +191,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
String rawComment = String rawComment =
"{@sym LAB_01003d2c} annotation at the beginning, middle {@sym LAB_01003d28} and " + "{@sym LAB_01003d2c} annotation at the beginning, middle {@sym LAB_01003d28} and " +
"end {@sym LAB_01003d5b}"; "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 " + assertEquals("{@sym 01003d2c} annotation at the beginning, middle {@sym 01003d28} and " +
"end {@sym 01003d5b}", fixed); "end {@sym 01003d5b}", fixed);
} }
@@ -192,7 +200,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testSymbolAnnotationWithValidAndInvalidSymbol() { public void testSymbolAnnotationWithValidAndInvalidSymbol() {
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " + String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " +
"with a {@sym FruityPebbles} 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 " + assertEquals("This is a symbol {@sym 01003d2c} annotation " +
"with a {@sym FruityPebbles} annotation.", fixed); "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 // 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 @Test
public void testUnknownAnnotation() { public void testUnknownAnnotation() {
String rawComment = "This is a symbol {@syyyybol bob} annotation"; String rawComment = "This is a symbol {@syyyybol bob} annotation";
@@ -917,6 +971,10 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
waitForSwing(); // let post-dialog processing happen waitForSwing(); // let post-dialog processing happen
} }
private Address addr(String offset) {
AddressFactory af = program.getAddressFactory();
return af.getAddress(offset);
}
//================================================================================================== //==================================================================================================
// Fake/Spy Classes // Fake/Spy Classes
//================================================================================================== //==================================================================================================

View File

@@ -16,8 +16,7 @@
package ghidra.program.database.function; package ghidra.program.database.function;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.database.symbol.SymbolDB; import ghidra.program.database.symbol.SymbolDB;
@@ -353,14 +352,13 @@ public abstract class VariableDB implements Variable {
} }
Variable otherVar = (Variable) obj; Variable otherVar = (Variable) obj;
if (!isEquivalent(otherVar)) { if (!isEquivalent(otherVar)) {
return false; return false;
} }
if (!StringUtils.equals(getName(), otherVar.getName())) { if (!Objects.equals(getName(), otherVar.getName())) {
return false; return false;
} }
return StringUtils.equals(getComment(), otherVar.getComment()); return Objects.equals(getComment(), otherVar.getComment());
} }
@Override @Override