GP-4037 Changes the various listing comment fields (PRE, POST, EOL, PLATE) to also display offcut comments, colored red

This commit is contained in:
ghidragon
2025-03-07 12:25:49 -05:00
parent b88a82dae1
commit 36b12ad8fe
9 changed files with 150 additions and 34 deletions
@@ -37,6 +37,7 @@ color.fg.listing.comment.ref.repeatable = color.palette.cornflowerblue
color.fg.listing.comment.plate = color.palette.gray
color.fg.listing.comment.post = color.palette.blue
color.fg.listing.comment.pre = color.palette.indigo
color.fg.listing.comment.offcut = color.fg.error
color.fg.listing.ref.bad = color.fg.error
color.fg.listing.ext.entrypoint = color.palette.magenta
color.fg.listing.ext.ref.resolved = color.palette.darkorange
@@ -73,6 +73,15 @@
<A href="help/topics/Annotations/Annotations.html"><B>annotations</B></A> to change the display
characteristics of data entered into the comment fields.</P>
<P><IMG src="help/shared/note.png" border="0">A comment can also be offcut. This means it is
defined on an address that is in the middle of an instruction or data item. In this case,
the offcut comment will be displayed in the appropriate field (EOL, PLATE, POST, PRE) on the
instruction or data that contains that address and will be colored red to indicate the comment
has been attached to an invalid address. Note that this comment can only be edited if the
containing instruction or data is first cleared so that the individual addresses are shown in
the listing.
</P>
<H2><A name="Edit_Comments"></A> <A name="Edit_Repeatable_Comment"></A> Adding or Editing
Comments (Set Comment)</H2>
@@ -4,9 +4,9 @@
* 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.
@@ -20,8 +20,7 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.support.RowColLocation;
import ghidra.app.util.viewer.field.EolEnablement;
import ghidra.app.util.viewer.field.EolExtraCommentsOption;
import ghidra.app.util.viewer.field.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
@@ -53,6 +52,7 @@ public class EolComments {
private List<RefRepeatComment> refRepeatables = new ArrayList<>();
private List<String> autos = new ArrayList<>();
private List<Reference> references = new ArrayList<>();
private List<String> offcuts = new ArrayList<>();
// used to signal the operand is already displaying a pointer reference, so there is no need for
// this class to create a comment to do the same
@@ -75,6 +75,7 @@ public class EolComments {
loadRepeatables();
loadRefRepeatables();
loadAutos();
loadOffcutEols();
}
/**
@@ -101,10 +102,15 @@ public class EolComments {
private void loadEols() {
Collection<String> comments =
Arrays.asList(codeUnit.getCommentAsArray(CodeUnit.EOL_COMMENT));
Arrays.asList(codeUnit.getCommentAsArray(CommentType.EOL));
addStrings(comments, eols);
}
private void loadOffcutEols() {
Collection<String> comments = CommentUtils.getOffcutComments(codeUnit, CommentType.EOL);
addStrings(comments, offcuts);
}
private void loadRepeatables() {
boolean hasOtherComments = !eols.isEmpty();
if (!extraCommentsOption.isShowingRepeatables(hasOtherComments)) {
@@ -195,6 +201,10 @@ public class EolComments {
return !autos.isEmpty();
}
public boolean isShowingOffcutComments() {
return !offcuts.isEmpty();
}
private Collection<String> getReferencePreviews() {
loadReferences();
@@ -642,6 +652,10 @@ public class EolComments {
return Collections.unmodifiableList(autos);
}
public List<String> getOffcutEolComments() {
return Collections.unmodifiableList(offcuts);
}
@Override
public String toString() {
@@ -28,8 +28,8 @@ import docking.widgets.fieldpanel.field.*;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui;
import ghidra.app.util.NamespaceUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.StringUtilities;
@@ -440,4 +440,49 @@ public class CommentUtils {
return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet());
}
/**
* Returns a list of offcut comments for the given code unit. All the offcut comments from
* possibly multiple addresses will be combined into a single list of comment lines.
* @param cu the code unit to get offcut comments for
* @param type the type of comment to retrieve (EOL, PRE, PLATE, POST)
* @return a list of all offcut comments for the given code unit.
*/
public static List<String> getOffcutComments(CodeUnit cu, CommentType type) {
// internal data items handle EOL comments, so ignore EOL comments on items that
// have sub-components
if (type == CommentType.EOL && cu instanceof Data data) {
if (data.getNumComponents() > 0) {
return Collections.emptyList();
}
}
Address start = cu.getMinAddress().next();
Address end = cu.getMaxAddress();
if (start == null || start.compareTo(end) > 0) {
return Collections.emptyList();
}
Listing listing = cu.getProgram().getListing();
AddressSet addrSet = new AddressSet(start, cu.getMaxAddress());
AddressIterator it = listing.getCommentAddressIterator(type, addrSet, true);
if (!it.hasNext()) {
return Collections.emptyList();
}
List<String> offcutComments = new ArrayList<>();
while (it.hasNext()) {
Address next = it.next();
String comment = listing.getComment(type, next);
if (comment != null) {
String[] lines = StringUtilities.toLines(comment);
for (String line : lines) {
offcutComments.add(line);
}
}
}
return offcutComments;
}
}
@@ -64,6 +64,7 @@ public class EolCommentFieldFactory extends FieldFactory {
private int repeatableCommentStyle;
private int automaticCommentStyle;
private int refRepeatableCommentStyle;
private int offcutCommentStyle;
private EolExtraCommentsOption extraCommentsOption = new EolExtraCommentsOption();
@@ -292,6 +293,14 @@ public class EolCommentFieldFactory extends FieldFactory {
elementList.addAll(elements);
}
if (comments.isShowingOffcutComments()) {
prefix = createPrefix(CommentStyle.OFFCUT);
int row = getNextRow(elementList);
List<String> offcuts = comments.getOffcutEolComments();
List<FieldElement> elements = convertToFieldElements(program, offcuts, prefix, row);
elementList.addAll(elements);
}
if (elementList.isEmpty()) {
return null;
}
@@ -316,11 +325,16 @@ public class EolCommentFieldFactory extends FieldFactory {
return new AttributedString(SEMICOLON_PREFIX, CommentColors.AUTO,
getMetrics(automaticCommentStyle), false, null);
}
if (commentStyle == CommentStyle.OFFCUT) {
return new AttributedString(SEMICOLON_PREFIX, CommentColors.OFFCUT,
getMetrics(style), false, null);
}
throw new AssertException("Unexected comment style: " + commentStyle);
}
private enum CommentStyle {
EOL, REPEATABLE, REF_REPEATABLE, AUTO;
EOL, REPEATABLE, REF_REPEATABLE, AUTO, OFFCUT;
}
private int getNextRow(List<FieldElement> elementList) {
@@ -4,9 +4,9 @@
* 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.
@@ -69,6 +69,7 @@ public class ListingColors {
public static final GColor PRE= new GColor("color.fg.listing.comment.pre");
public static final GColor REPEATABLE = new GColor("color.fg.listing.comment.repeatable");
public static final GColor REF_REPEATABLE = new GColor("color.fg.listing.comment.ref.repeatable");
public static final GColor OFFCUT = new GColor("color.fg.listing.comment.offcut");
}
public static class LabelColors {
@@ -4,9 +4,9 @@
* 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.
@@ -40,6 +40,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import util.CollectionUtils;
/**
* Class for showing plate comments
@@ -142,12 +143,14 @@ public class PlateFieldFactory extends FieldFactory {
CodeUnit cu = (CodeUnit) proxy.getObject();
boolean isClipped = false;
List<FieldElement> elements = new ArrayList<>();
String commentText = getCommentText(cu);
List<String> offcutComments = CommentUtils.getOffcutComments(cu, CommentType.PLATE);
String commentText = getCommentText(cu, offcutComments);
if (StringUtils.isBlank(commentText)) {
getDefaultFieldElements(cu, elements);
}
else {
isClipped = getFormattedFieldElements(cu, elements);
isClipped = getFormattedFieldElements(cu, elements, offcutComments);
}
if (elements.isEmpty()) {
@@ -170,14 +173,15 @@ public class PlateFieldFactory extends FieldFactory {
return listingField;
}
private boolean getFormattedFieldElements(CodeUnit cu, List<FieldElement> elements) {
private boolean getFormattedFieldElements(CodeUnit cu, List<FieldElement> elements,
List<String> offcutComments) {
int numberBlankLines = getNumberBlankLines(cu, true);
addBlankLines(elements, numberBlankLines, cu);
String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT);
return generateFormattedPlateComment(elements, comments, cu.getProgram());
return generateFormattedPlateComment(elements, comments, offcutComments, cu.getProgram());
}
private void getDefaultFieldElements(CodeUnit cu, List<FieldElement> elements) {
@@ -205,7 +209,7 @@ public class PlateFieldFactory extends FieldFactory {
return false;
}
private String getCommentText(CodeUnit cu) {
private String getCommentText(CodeUnit cu, List<String> offcutComments) {
String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT);
if (comments == null) {
return null;
@@ -218,6 +222,12 @@ public class PlateFieldFactory extends FieldFactory {
}
buffy.append(comment);
}
for (String offcut : offcutComments) {
if (buffy.length() != 0) {
buffy.append('\n');
}
buffy.append(offcut);
}
return buffy.toString();
}
@@ -226,8 +236,8 @@ public class PlateFieldFactory extends FieldFactory {
* data is clipped because it is too long to display.
*/
private boolean generateFormattedPlateComment(List<FieldElement> elements, String[] comments,
Program p) {
if (comments == null || comments.length == 0) {
List<String> offcutComments, Program p) {
if (offcutComments.isEmpty() && CollectionUtils.isBlank(comments)) {
return false;
}
@@ -245,6 +255,11 @@ public class PlateFieldFactory extends FieldFactory {
for (String c : comments) {
commentsList.add(CommentUtils.parseTextForAnnotations(c, p, prototype, row++));
}
for (String offcut : offcutComments) {
AttributedString as = new AttributedString(offcut, CommentColors.OFFCUT,
getMetrics(style), false, null);
commentsList.add(new TextFieldElement(as, commentsList.size(), 0));
}
if (isWordWrap) {
int spaceWidth = getMetrics().charWidth(' ');
@@ -554,7 +569,8 @@ public class PlateFieldFactory extends FieldFactory {
*/
CodeUnit cu = (CodeUnit) obj;
String commentText = getCommentText(cu);
List<String> offcutComments = CommentUtils.getOffcutComments(cu, CommentType.PLATE);
String commentText = getCommentText(cu, offcutComments);
boolean hasComment = true;
if (StringUtils.isBlank(commentText)) {
String defaultComment = getDefaultComment(cu);
@@ -4,9 +4,9 @@
* 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.
@@ -137,21 +137,22 @@ public class PostCommentFieldFactory extends FieldFactory {
}
String[] autoComment = getAutoPostComment(cu);
List<String> offcutComments = CommentUtils.getOffcutComments(cu, CommentType.POST);
String[] comments = cu.getCommentAsArray(CodeUnit.POST_COMMENT);
if (comments != null && comments.length > 0 && (cu instanceof Data)) {
return getTextField(comments, autoComment, proxy, x, false);
return getTextField(comments, autoComment, offcutComments, proxy, x, false);
}
if (cu instanceof Instruction) {
Instruction instr = (Instruction) cu;
if (instr.getDelaySlotDepth() > 0) {
if (comments != null && comments.length > 0) {
return getTextField(comments, null, proxy, x, false);
return getTextField(comments, null, offcutComments, proxy, x, false);
}
return null;
}
// check field options
return getTextFieldForOptions(instr, comments, autoComment, proxy, x);
return getTextFieldForOptions(instr, comments, autoComment, offcutComments, proxy, x);
}
return null;
}
@@ -386,7 +387,7 @@ public class PostCommentFieldFactory extends FieldFactory {
}
private ListingTextField getTextFieldForOptions(Instruction instr, String[] comments,
String[] autoComment, ProxyObj<?> proxy, int xStart) {
String[] autoComment, List<String> offcutComments, ProxyObj<?> proxy, int xStart) {
Listing listing = instr.getProgram().getListing();
Address addr = instr.getMinAddress();
FlowType flowType = instr.getFlowType();
@@ -405,14 +406,14 @@ public class PostCommentFieldFactory extends FieldFactory {
String[] str = new String[] {
FUN_EXIT_FLAG_LEADER + function.getName() + FUN_EXIT_FLAG_TAIL };
return getTextField(str, autoComment, proxy, xStart, true);
return getTextField(str, autoComment, offcutComments, proxy, xStart, true);
}
}
}
// Add Jump/Terminator
if (flagJMPsRETs && !instr.hasFallthrough()) {
String[] str = new String[] { DEFAULT_FLAG_COMMENT };
return getTextField(str, autoComment, proxy, xStart, true);
return getTextField(str, autoComment, offcutComments, proxy, xStart, true);
}
}
@@ -472,11 +473,12 @@ public class PostCommentFieldFactory extends FieldFactory {
}
}
}
return getTextField(comments, autoComment, proxy, xStart, false);
return getTextField(comments, autoComment, offcutComments, proxy, xStart, false);
}
private ListingTextField getTextField(String[] comments, String[] autoComment,
ProxyObj<?> proxy, int xStart, boolean useLinesAfterBlock) {
List<String> offcutComments, ProxyObj<?> proxy, int xStart,
boolean useLinesAfterBlock) {
if (comments == null) {
comments = EMPTY_STRING_ARRAY;
@@ -489,7 +491,8 @@ public class PostCommentFieldFactory extends FieldFactory {
((comments.length == 0 && !useLinesAfterBlock) || alwaysShowAutomatic)
? autoComment.length
: 0;
if (!useLinesAfterBlock && comments.length == 0 && nLinesAutoComment == 0) {
if (!useLinesAfterBlock && comments.length == 0 && nLinesAutoComment == 0 &&
offcutComments.isEmpty()) {
return null;
}
@@ -507,6 +510,11 @@ public class PostCommentFieldFactory extends FieldFactory {
fields.add(CommentUtils.parseTextForAnnotations(comment, program, prototypeString,
fields.size()));
}
for (String offcutComment : offcutComments) {
AttributedString as = new AttributedString(offcutComment, CommentColors.OFFCUT,
getMetrics(style), false, null);
fields.add(new TextFieldElement(as, fields.size(), 0));
}
if (isWordWrap) {
fields = FieldUtils.wrap(fields, width);
}
@@ -112,8 +112,9 @@ public class PreCommentFieldFactory extends FieldFactory {
String[] autoComment = getAutoPreComments(cu);
String[] comments = getDefinedPreComments(cu);
List<String> offcutComments = CommentUtils.getOffcutComments(cu, CommentType.PRE);
return getTextField(comments, autoComment, proxy, x);
return getTextField(comments, autoComment, offcutComments, proxy, x);
}
private String[] getDefinedPreComments(CodeUnit cu) {
@@ -353,7 +354,7 @@ public class PreCommentFieldFactory extends FieldFactory {
}
private ListingTextField getTextField(String[] comments, String[] autoComment,
ProxyObj<?> proxy, int xStart) {
List<String> offcutComments, ProxyObj<?> proxy, int xStart) {
if (comments == null) {
comments = EMPTY_STRING_ARRAY;
@@ -364,7 +365,8 @@ public class PreCommentFieldFactory extends FieldFactory {
int nLinesAutoComment =
(comments.length == 0 || alwaysShowAutomatic) ? autoComment.length : 0;
if (comments.length == 0 && nLinesAutoComment == 0) {
if (comments.length == 0 && nLinesAutoComment == 0 && offcutComments.isEmpty()) {
return null;
}
@@ -382,6 +384,12 @@ public class PreCommentFieldFactory extends FieldFactory {
fields.add(CommentUtils.parseTextForAnnotations(comment, program, prototypeString,
fields.size()));
}
for (String offcutComment : offcutComments) {
AttributedString as = new AttributedString(offcutComment, CommentColors.OFFCUT,
getMetrics(style), false, null);
fields.add(new TextFieldElement(as, fields.size(), 0));
}
if (isWordWrap) {
fields = FieldUtils.wrap(fields, width);
}