Merge remote-tracking branch 'origin/GP-6414-dragonmacher-annotations-fix-auto-comments--SQUASHED' into patch

This commit is contained in:
Ryan Kurtz
2026-02-09 04:51:22 -05:00
4 changed files with 209 additions and 73 deletions

View File

@@ -40,6 +40,28 @@ import util.CollectionUtils;
* Repeatable comment for the code unit, any repeatable comments for the code units that this code
* unit has references to, and possibly a comment indicating the data at a code unit that is
* referenced by this code unit.
*
* This section describes the various EOL comment types that may appear. The user can
* toggle which types are enabled. The comments are displayed in the order listed below.
*
* EOL Types:
*
* - EOL - user end of line comment
*
* - Repeatable - user repeatable source comment *at the code unit*
* - Ref Repeatable- for every reference *from a code unit*, show the target:
* - address repeatable,
* - function repeatable,
* - code unit repeatable
*
* - Auto - fabricated reference preview:
* - function,
* - indirect data pointer,
* - direct data access preview
* *depending on the options, this typically do not appear when
* repeatable comments exist
*
* - Offcut - comments at addresses inside of a code unit
*/
public class EolComments {
@@ -67,6 +89,7 @@ public class EolComments {
this.operandsShowReferences = operandsShowReferences;
this.maxDisplayComments = maxDisplayComments;
this.extraCommentsOption = extraCommentsOption;
loadComments();
}

View File

@@ -17,8 +17,8 @@ package ghidra.app.util.viewer.field;
import java.awt.Color;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.*;
@@ -26,6 +26,7 @@ import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.util.*;
import ghidra.app.util.viewer.field.ListingColors.CommentColors;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.app.util.viewer.options.OptionsGui;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.*;
@@ -33,8 +34,10 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.StringUtilities;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.exception.AssertException;
import utility.function.Dummy;
/**
* Generates End of line comment Fields.
@@ -236,6 +239,7 @@ public class EolCommentFieldFactory extends FieldFactory {
if (!enabled || !(obj instanceof CodeUnit)) {
return null;
}
CodeUnit cu = (CodeUnit) obj;
Program program = cu.getProgram();
@@ -244,14 +248,8 @@ public class EolCommentFieldFactory extends FieldFactory {
// comments if open. If this was allowed, then the comment would appear
// on the outside data container and on the 1st internal member
//
if (cu instanceof Data) {
Data data = (Data) cu;
if (data.getNumComponents() > 0) {
boolean isOpen = proxy.getListingLayoutModel().isOpen((Data) proxy.getObject());
if (isOpen) {
return null; // avoid double showing
}
}
if (isOpenData(cu, proxy)) {
return null;
}
EolComments comments = new EolComments(cu, codeUnitFormatOptions.followReferencedPointers(),
@@ -261,25 +259,59 @@ public class EolCommentFieldFactory extends FieldFactory {
List<FieldElement> elementList = new ArrayList<>();
AttributedString prefix = createPrefix(CommentStyle.EOL);
List<String> eols = comments.getEOLComments();
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0);
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0, true);
elementList.addAll(eolElements);
/*
This section describes the various EOL comment types that may appear. The user can
toggle which types are enabled. The comments are displayed in the order listed below.
EOL Types:
- EOL - user end of line comment
- Repeatable - user repeatable source comment *at the code unit*
- Ref Repeatable- for every reference *from a code unit*, show the target:
- address repeatable,
- function repeatable,
- code unit repeatable
- Auto - fabricated reference preview:
- function,
- indirect data pointer,
- direct data access preview
*depending on the options, this typically do not appear when
repeatable comments exist
- Offcut - comments at addresses inside of a code unit
*/
if (comments.isShowingRepeatables()) {
prefix = createPrefix(CommentStyle.REPEATABLE);
int row = getNextRow(elementList);
List<String> repeatables = comments.getRepeatableComments();
List<FieldElement> elements = convertToFieldElements(program, repeatables, prefix, row);
List<FieldElement> elements =
convertToFieldElements(program, repeatables, prefix, row, true);
elementList.addAll(elements);
}
if (comments.isShowingRefRepeatables()) {
prefix = createPrefix(CommentStyle.REF_REPEATABLE);
AttributedString refPrefix = createPrefix(CommentStyle.REF_REPEATABLE);
List<RefRepeatComment> refRepeatables = comments.getReferencedRepeatableComments();
for (RefRepeatComment comment : refRepeatables) {
int row = getNextRow(elementList);
String[] lines = comment.getCommentLines();
Address refAddress = comment.getAddress();
List<String> linesList = Arrays.asList(lines);
Consumer<List<FieldElement>> decorator = elements -> {
prependRefAddress(program, refPrefix, refAddress, elements);
};
List<FieldElement> elements =
convertToRefFieldElements(lines, program, prefix, comment.getAddress(), row);
convertToFieldElements(program, linesList, decorator, prefix, row, true);
elementList.addAll(elements);
}
}
@@ -288,7 +320,11 @@ public class EolCommentFieldFactory extends FieldFactory {
prefix = createPrefix(CommentStyle.AUTO);
int row = getNextRow(elementList);
List<String> autos = comments.getAutomaticComment();
List<FieldElement> elements = convertToFieldElements(program, autos, prefix, row);
// Note: we pass 'false' for allowing annotations so that the user will see the raw data
// and not an interpreted annotation.
List<FieldElement> elements =
convertToFieldElements(program, autos, prefix, row, false);
elementList.addAll(elements);
}
@@ -296,7 +332,8 @@ public class EolCommentFieldFactory extends FieldFactory {
prefix = createPrefix(CommentStyle.OFFCUT);
int row = getNextRow(elementList);
List<String> offcuts = comments.getOffcutEolComments();
List<FieldElement> elements = convertToFieldElements(program, offcuts, prefix, row);
List<FieldElement> elements =
convertToFieldElements(program, offcuts, prefix, row, true);
elementList.addAll(elements);
}
@@ -307,6 +344,18 @@ public class EolCommentFieldFactory extends FieldFactory {
maxDisplayLines, hlProvider);
}
private boolean isOpenData(CodeUnit cu, ProxyObj<?> proxy) {
if (cu instanceof Data data) {
if (data.getNumComponents() > 0) {
ListingModel listingModel = proxy.getListingLayoutModel();
if (listingModel.isOpen(data)) {
return true;
}
}
}
return false;
}
private AttributedString createPrefix(CommentStyle commentStyle) {
if (commentStyle == CommentStyle.EOL) {
return new AttributedString(SEMICOLON_PREFIX, CommentColors.EOL, getMetrics(style),
@@ -349,7 +398,15 @@ public class EolCommentFieldFactory extends FieldFactory {
}
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
AttributedString prefix, int row) {
AttributedString prefix, int row, boolean allowAnnotations) {
Consumer<List<FieldElement>> decorator = Dummy.consumer(); // no decorations by default
return convertToFieldElements(program, comments, decorator, prefix, row, allowAnnotations);
}
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
Consumer<List<FieldElement>> decorator, AttributedString prefix, int row,
boolean allowAnnotations) {
List<FieldElement> fieldElements = new ArrayList<>();
if (comments.isEmpty()) {
@@ -357,11 +414,15 @@ public class EolCommentFieldFactory extends FieldFactory {
}
for (int commentRow = 0; commentRow < comments.size(); commentRow++) {
int offsetRow = row + commentRow;
fieldElements.add(CommentUtils.parseTextForAnnotations(comments.get(commentRow),
program, prefix, offsetRow));
int encodedRow = row + commentRow;
String commentText = comments.get(commentRow);
FieldElement element =
createCommentField(commentText, prefix, program, encodedRow, allowAnnotations);
fieldElements.add(element);
}
decorator.accept(fieldElements);
if (isWordWrap) {
int lineWidth = showSemicolon ? width - prefix.getStringWidth() : width;
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
@@ -381,57 +442,43 @@ public class EolCommentFieldFactory extends FieldFactory {
return fieldElements;
}
private List<FieldElement> convertToRefFieldElements(String[] comments, Program program,
AttributedString currentPrefixString, Address refAddress, int nextRow) {
private FieldElement createCommentField(String commentText, AttributedString prototype,
Program program, int row, boolean allowAnnotations) {
int numCommentLines = comments.length;
List<FieldElement> fieldElements = new ArrayList<>();
if (numCommentLines == 0) {
return fieldElements;
}
for (int rowIndex = 0; rowIndex < numCommentLines; rowIndex++) {
int encodedRow = nextRow + rowIndex;
fieldElements.add(CommentUtils.parseTextForAnnotations(comments[rowIndex], program,
currentPrefixString, encodedRow));
}
if (prependRefAddress) {
FieldElement commentElement = fieldElements.get(0);
// Address
String refAddrComment = "{@address " + refAddress.toString() + "}";
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
int encodedRow = startRowCol.row();
int encodedCol = startRowCol.col();
Annotation annotation = new Annotation(refAddrComment, program);
FieldElement addressElement =
new AnnotatedTextFieldElement(annotation, currentPrefixString, program, encodedRow,
encodedCol);
// Space character
AttributedString spaceStr = new AttributedString(" ", currentPrefixString.getColor(0),
currentPrefixString.getFontMetrics(0), false, null);
FieldElement spacerElement = new TextFieldElement(spaceStr, encodedRow, encodedCol);
fieldElements.add(new CompositeFieldElement(
new FieldElement[] { addressElement, spacerElement, commentElement }));
if (allowAnnotations) {
return CommentUtils.parseTextForAnnotations(commentText, program, prototype, row);
}
if (isWordWrap) {
int lineWidth = showSemicolon ? width - currentPrefixString.getStringWidth() : width;
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
String text = StringUtilities.convertTabsToSpaces(commentText);
AttributedString as = new AttributedString(text, prototype.getColor(0),
prototype.getFontMetrics(0), false, null);
return new TextFieldElement(as, row, 0);
}
private void prependRefAddress(Program program, AttributedString prefix, Address refAddress,
List<FieldElement> fieldElements) {
if (!prependRefAddress) {
return;
}
if (showSemicolon) {
for (int i = 0; i < fieldElements.size(); i++) {
RowColLocation startRowCol =
fieldElements.get(i).getDataLocationForCharacterIndex(0);
int encodedRow = startRowCol.row();
int encodedCol = startRowCol.col();
FieldElement prefixFieldElement =
new TextFieldElement(currentPrefixString, encodedRow, encodedCol);
fieldElements.set(i, new CompositeFieldElement(
new FieldElement[] { prefixFieldElement, fieldElements.get(i) }));
}
}
return fieldElements;
FieldElement commentElement = fieldElements.get(0);
// Address
String refAddrComment = "{@address " + refAddress.toString() + "}";
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
int encodedRow = startRowCol.row();
int encodedCol = startRowCol.col();
Annotation annotation = new Annotation(refAddrComment, program);
FieldElement addressElement =
new AnnotatedTextFieldElement(annotation, prefix, program, encodedRow, encodedCol);
// Space character
AttributedString spaceStr = new AttributedString(" ", prefix.getColor(0),
prefix.getFontMetrics(0), false, null);
FieldElement spacerElement = new TextFieldElement(spaceStr, encodedRow, encodedCol);
fieldElements.set(0, new CompositeFieldElement(
new FieldElement[] { addressElement, spacerElement, commentElement }));
}
/**

View File

@@ -866,6 +866,18 @@ public class ProgramBuilder {
}
}
/**
* Creates a non-null-terminated ascii string at the given address
* @param address the address
* @param string the string
* @return the new data
* @throws Exception if there is an exception
*/
public Data createString(String address, String string) throws Exception {
return createString(address, string, StandardCharsets.US_ASCII, false,
StringDataType.dataType);
}
public Data createString(String address, String string, Charset charset, boolean nullTerminate,
DataType dataType) throws Exception {
if (nullTerminate) {

View File

@@ -15,6 +15,8 @@
*/
package ghidra.app.util.viewer.field;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.*;
import static org.hamcrest.core.StringStartsWith.*;
import static org.junit.Assert.*;
@@ -29,10 +31,17 @@ import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.test.*;
public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
private static final String STRING_ADDRESS_WITH_ANNOTATION = "0x01002c98";
private static final String ADDRESS_CALLING_STRING_WITH_ANNOTATION = "0X01002d37";
private static final String AUTO_COMMENT_TEXT_WITH_ANNOTATION =
"Annotation: {@address 12345678 foo}";
private TestEnv env;
private CodeBrowserPlugin cb;
private Options fieldOptions;
@@ -75,21 +84,45 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
public void testRepeatableComment_FunctionCall() throws Exception {
// check existing auto comment
ListingTextField tf = getFieldText(addr("0x010022e6"));
String from = "0x010022e6";
ListingTextField tf = getFieldText(addr(from));
assertEquals(1, tf.getNumRows());
assertThat(tf.getText(), startsWith("undefined ghidra(undefined4 param_1,"));
// set repeatable comment at destination
Address destination = addr("0x01002cf5");
// set repeatable comment at source
String to = "0x01002cf5";
String repeatableComment = "My repeatable comment";
setRepeatableComment(destination, repeatableComment);
setRepeatableComment(addr(to), repeatableComment);
// check that the auto comment now matches the updated comment
tf = getFieldText(addr("0x010022e6"));
// check that the repeatable comment now matches the updated comment
tf = getFieldText(addr(from));
assertEquals(1, tf.getNumRows());
assertEquals(tf.getText(), repeatableComment);
}
@Test
public void testRepeatableComment_FunctionCall_PrependRefAddress() throws Exception {
setBooleanOption(EolCommentFieldFactory.ENABLE_PREPEND_REF_ADDRESS_KEY, true);
// check existing auto comment
String from = "0x010022e6";
ListingTextField tf = getFieldText(addr(from));
assertEquals(1, tf.getNumRows());
assertThat(tf.getText(), startsWith("undefined ghidra(undefined4 param_1,"));
// set repeatable comment at source
String to = "0x01002cf5";
String repeatableComment = "My repeatable comment";
setRepeatableComment(addr(to), repeatableComment);
// check that the repeatable comment now matches the updated comment and has the ref address
// prepended
tf = getFieldText(addr(from));
assertEquals(1, tf.getNumRows());
assertEquals("01002cf5 " + repeatableComment, tf.getText());
}
@Test
public void testRepeatableComment_DataAccess() throws Exception {
@@ -106,7 +139,21 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
// check that the auto comment now matches the updated comment
tf = getFieldText(addr("0x01002265"));
assertEquals(1, tf.getNumRows());
assertEquals(tf.getText(), repeatableComment);
assertEquals(repeatableComment, tf.getText());
}
@Test
public void testAutoCommentDoesNotRenderAnnotation() {
/*
Creates a data reference to a string containing an annotation. Tests that the
annotation is not rendered, but is shown in its raw form.
*/
goTo(env.getTool(), program, STRING_ADDRESS_WITH_ANNOTATION);
ListingTextField tf = getFieldText(addr(ADDRESS_CALLING_STRING_WITH_ANNOTATION));
assertEquals(1, tf.getNumRows());
assertThat(tf.getText(), containsString(AUTO_COMMENT_TEXT_WITH_ANNOTATION));
}
//==================================================================================================
@@ -115,6 +162,13 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
private ProgramDB buildProgram() throws Exception {
ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder();
builder.createString(STRING_ADDRESS_WITH_ANNOTATION, AUTO_COMMENT_TEXT_WITH_ANNOTATION);
builder.createMemoryReference(ADDRESS_CALLING_STRING_WITH_ANNOTATION,
STRING_ADDRESS_WITH_ANNOTATION,
RefType.DATA, SourceType.ANALYSIS);
return builder.getProgram();
}