Merge remote-tracking branch 'origin/GP-3531-dragonmacher-update-auto-comments--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-10-16 16:45:14 -04:00
commit 5aedf25ee6
18 changed files with 1580 additions and 1252 deletions

View File

@ -518,24 +518,46 @@
<H3><A name="EOL_Comments_Field"></A>EOL Comments Field</H3>
<BLOCKQUOTE>
<P>The EOL Comments field displays the end-of-line comment at this address. If there is no
EOL comment, it displays the repeatable comment. If there is no repeatable comment, it
displays the repeatable comments from all referenced addresses. If there aren't any
referenced repeatable comments, it displays an automatic comment if it can.</P>
<P>The EOL Comments field displays the end-of-line comment at this address. By default,
if there is no EOL comment, then other comment types may be displayed.
</P>
<P><B>Always Show the Automatic Comment -</B> Normally automatic comments are not shown if
there is an EOL comment, repeatable comment, or referenced repeatable comment. By selecting
this option, the automatic comment will be shown even if there is an EOL comment,
repeatable comment, or referenced repeatable comment.</P>
<P><B>Additional Comment Types -</B> The following comment types may be optionally displayed
in the EOL Comments field. For each type below, the following setting may be applied:
<UL>
<LI><B>ALWAYS -</B> Always show the comment type, regardless of other comment types
that are showing. The appearance of any comment will be limited by the maximum
number of lines currently set on the EOL Comments field.
</LI>
<LI><B>DEFAULT -</B> Show the comment type only when there is no other comment type
of a higher precedence. When all fields are set to this option, then the EOL
Comments field will only display one comment type at a time.
</LI>
<LI><B>NEVER -</B> Do not show this comment type.
</LI>
</UL>
</P>
<P>
Each of the comments below is listed in order of precedence. The default behavior is to
show one comment in the EOL Comments field at a time, based on this precedence, with the EOL
Comment being the highest.
</P>
<BLOCKQUOTE>
<P><B>Repeatable -</B> The Repeatable Comment defined <I>at the current
code unit address</I>.</P>
<P><B>Referenced Repeatable -</B> The Repeatable Comment that is defined at the
target reference address that the code unit at this address refers to.</P>
<P><B>Automatic Function -</B> A preview of the referenced function.</P>
<P><B>Automatic Data -</B> A preview of the referenced data. For example, a String
reference will show a preview of the target String.</P>
</BLOCKQUOTE>
<P><B>Always Show the Referenced Repeatable Comment -</B> Normally referenced repeatable
comments are not shown if there is an EOL comment or repeatable comment. By selecting this
option, the referenced repeatable comment will be shown even if there is an EOL comment or
repeatable comment.</P>
<P><B>Always Show the Repeatable Comment -</B> Normally repeatable comments are not shown
if there is an EOL comment. By selecting this option, the repeatable comment will be shown
even if there is an EOL comment.</P>
<P><B>Enable Word Wrapping -</B> If this option is not set, each comment line is displayed
on a line by itself. By turning on word-wrapping, comment lines are displayed in paragraph

View File

@ -1,927 +0,0 @@
/* ###
* 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;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.support.RowColLocation;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.StringUtilities;
/**
* Utility class with methods to get comment information that can be displayed in the
* end of line comment field. A DisplayableEol is associated with a code unit.
* The DisplayableEol gets information for the EOL comment field, which can show the
* End of Line comment for the code unit, the 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.
*/
public class DisplayableEol {
private static final String POINTER_ARROW = "-> ";
public static final int MY_EOLS = 0;
public static final int MY_REPEATABLES = 1;
public static final int REF_REPEATABLES = 2;
public static final int MY_AUTOMATIC = 3;
private CodeUnit codeUnit;
private Object[][] displayCommentArrays = { null, null, null, null };
private boolean alwaysShowRepeatable = false;
private boolean alwaysShowRefRepeats = false;
private boolean alwaysShowAutomatic = false;
private boolean showAutomaticFunctions;
private boolean operandsFollowPointerRefs = false;
private int maxDisplayLines;
private int totalCommentsFound;
private boolean useAbbreviatedAutomatic;
public DisplayableEol(CodeUnit cu, boolean alwaysShowRepeatable, boolean alwaysShowRefRepeats,
boolean alwaysShowAutomatic, boolean operandsFollowPointerRefs, int maxDisplayLines,
boolean useAbbreviatedAutomatic, boolean showAutomaticFunctions) {
this.codeUnit = cu;
this.alwaysShowRepeatable = alwaysShowRepeatable;
this.alwaysShowRefRepeats = alwaysShowRefRepeats;
this.alwaysShowAutomatic = alwaysShowAutomatic;
this.operandsFollowPointerRefs = operandsFollowPointerRefs;
this.maxDisplayLines = maxDisplayLines;
this.useAbbreviatedAutomatic = useAbbreviatedAutomatic;
this.showAutomaticFunctions = showAutomaticFunctions;
initComments();
}
private void initComments() {
displayCommentArrays[MY_EOLS] = codeUnit.getCommentAsArray(CodeUnit.EOL_COMMENT);
totalCommentsFound += displayCommentArrays[MY_EOLS].length;
displayCommentArrays[MY_REPEATABLES] =
codeUnit.getCommentAsArray(CodeUnit.REPEATABLE_COMMENT);
totalCommentsFound += displayCommentArrays[MY_REPEATABLES].length;
displayCommentArrays[REF_REPEATABLES] = new RefRepeatComment[0];
displayCommentArrays[MY_AUTOMATIC] = new String[0];
if (totalCommentsFound > maxDisplayLines) {
// no more room to display the comments below; don't process them
return;
}
// cap the number of references we get (we don't want to process 500000....)
Reference[] refs = getReferencesFrom(codeUnit, 100);
Arrays.sort(refs);
Program program = codeUnit.getProgram();
displayCommentArrays[REF_REPEATABLES] =
getRepeatableComments(program.getListing(), refs, true);
totalCommentsFound += displayCommentArrays[REF_REPEATABLES].length;
displayCommentArrays[MY_AUTOMATIC] = getReferencePreviews(program, refs);
totalCommentsFound += displayCommentArrays[MY_AUTOMATIC].length;
}
private Reference[] getReferencesFrom(CodeUnit cu, int maxReferences) {
ArrayList<Reference> list = new ArrayList<>();
Program program = cu.getProgram();
ReferenceManager referenceManager = program.getReferenceManager();
AddressSet set = new AddressSet(cu.getMinAddress(), cu.getMaxAddress());
AddressIterator iter = referenceManager.getReferenceSourceIterator(set, true);
while (iter.hasNext() && list.size() < maxReferences) {
Address fromAddress = iter.next();
Reference[] refs = referenceManager.getReferencesFrom(fromAddress);
for (Reference element : refs) {
list.add(element);
}
}
return list.toArray(new Reference[list.size()]);
}
/**
* Return whether the associated code unit has an end of line comment
* @return whether the associated code unit has an end of line comment
*/
public boolean hasEOL() {
return (displayCommentArrays[MY_EOLS] != null) &&
(((String[]) displayCommentArrays[MY_EOLS]).length > 0);
}
/**
* Return whether the associated code unit has a repeatable comment
* @return whether the associated code unit has a repeatable comment
*/
public boolean hasRepeatable() {
return (displayCommentArrays[MY_REPEATABLES] != null) &&
(((String[]) displayCommentArrays[MY_REPEATABLES]).length > 0);
}
/**
* Return whether any memory reference from this code unit has a repeatable
* comment at the reference's to address
* @return whether any memory reference from this code unit has a repeatable
* comment at the reference's to address
*/
public boolean hasReferencedRepeatable() {
return (displayCommentArrays[REF_REPEATABLES] != null) &&
(displayCommentArrays[REF_REPEATABLES].length > 0);
}
/**
* Return whether this code unit has an automatic comment. For example, a memory reference
* from this code unit has a function defined at the reference's to address, or if the to
* address is a pointer.
* @return whether this code unit has an automatic comment
*/
public boolean hasAutomatic() {
return (displayCommentArrays[MY_AUTOMATIC] != null) &&
(displayCommentArrays[MY_AUTOMATIC].length > 0);
}
private String[] getReferencePreviews(Program program, Reference[] refs) {
if (refs.length == 0) {
return getPreviewForNoReferences();
}
Set<String> set = new LinkedHashSet<>();
for (Reference reference : refs) {
if (reachedMaximumResults(set.size())) {
break;
}
if (!isValidReference(program, reference)) {
continue;
}
addReferencePreview(set, program, reference);
}
String[] array = new String[set.size()];
set.toArray(array);
return array;
}
private String[] getPreviewForNoReferences() {
String translatedStringValue = getPreviewForString();
if (translatedStringValue != null) {
return new String[] { translatedStringValue };
}
String undefinedPointerText = getUndefinedPointer(codeUnit);
if (undefinedPointerText != null) {
return new String[] { undefinedPointerText };
}
return new String[0];
}
private String getPreviewForString() {
if (codeUnit instanceof Data data && StringDataInstance.isString(data)) {
StringDataInstance sdi = StringDataInstance.getStringDataInstance(data);
if (sdi.hasTranslatedValue()) {
// show the opposite value
return sdi.getStringRepresentation(sdi.isShowTranslation());
}
}
return null;
}
private boolean isValidReference(Program program, Reference reference) {
if (!reference.isMemoryReference()) {
return false;
}
Address toAddr = reference.getToAddress();
return isGoodAddress(program, toAddr);
}
private boolean reachedMaximumResults(int newCount) {
return (totalCommentsFound + newCount) >= maxDisplayLines;
}
private void addReferencePreview(Set<String> results, Program program, Reference reference) {
Address toAddr = reference.getToAddress();
if (handleDirectFlow(results, reference, program, toAddr)) {
return;
}
Data data = getData(program, toAddr);
if (data == null) {
return; // nothing there!
}
if (handleIndirectDataReference(results, reference, program, toAddr, data)) {
return;
}
handleDirectDataReference(results, toAddr, data);
}
private Data getData(Program program, Address toAddr) {
Data data = program.getListing().getDataAt(toAddr);
if (data == null) {
// could be slower
data = program.getListing().getDataContaining(toAddr);
}
return data;
}
private void handleDirectDataReference(Set<String> set, Address dataAccessAddress, Data data) {
Object value = data.getValue();
if (value instanceof Scalar) {
Scalar scalar = (Scalar) value;
if (scalar.getSignedValue() == 0) {
return;
}
}
String dataRepresentation = getDataValueRepresentation(dataAccessAddress, data);
if (!StringUtils.isBlank(dataRepresentation)) {
set.add("= " + dataRepresentation);
}
}
private String getDataValueRepresentation(Address dataAccessAddress, Data data) {
if (!useAbbreviatedAutomatic) {
return data.getDefaultValueRepresentation();
}
if (isOffcut(dataAccessAddress, data)) {
return getOffcutDataString(dataAccessAddress, data);
}
return data.getDefaultValueRepresentation();
}
private boolean isOffcut(Address address, CodeUnit cu) {
if (cu == null) {
return false;
}
return !cu.getMinAddress().equals(address);
}
private String getOffcutDataString(Address offcutAddress, Data data) {
Address dataAddress = data.getMinAddress();
int diff = (int) offcutAddress.subtract(dataAddress);
DataType dt = data.getBaseDataType();
return getOffcutForStringData(data, dataAddress, diff, dt);
}
private String getOffcutForStringData(Data data, Address dataAddress, int diff, DataType dt) {
if (StringDataInstance.isString(data)) {
StringDataInstance string = StringDataInstance.getStringDataInstance(data);
string = string.getByteOffcut(diff);
return string.getStringRepresentation();
}
if (!data.hasStringValue()) {
return null;
}
int len = data.getLength();
if (diff >= len) {
// not sure if this can happen--just use the default
return data.getDefaultValueRepresentation();
}
DumbMemBufferImpl mb = new DumbMemBufferImpl(data.getMemory(), dataAddress.add(diff));
String s = dt.getRepresentation(mb, data, len - diff);
return s;
}
private boolean handleIndirectDataReference(Set<String> set, Reference reference,
Program program, Address toAddress, Data data) {
RefType type = reference.getReferenceType();
if (!type.isIndirect()) {
return false;
}
if (handlePointer(set, program, reference, data)) {
return true;
}
handlePotentialPointer(set, program, toAddress, data);
return true;
}
private boolean handlePointer(Set<String> set, Program program, Reference reference,
Data data) {
if (!data.isPointer()) {
return false;
}
SymbolTable symbolTable = program.getSymbolTable();
ReferenceManager referenceManager = program.getReferenceManager();
Reference pointerReference =
referenceManager.getPrimaryReferenceFrom(reference.getToAddress(), 0);
if (pointerReference != null) {
Address addr = pointerReference.getToAddress();
Symbol sym = symbolTable.getPrimarySymbol(addr);
if (operandsFollowPointerRefs && reference.getOperandIndex() != CodeUnit.MNEMONIC) {
if (!sym.isDynamic()) {
return true; // already displayed by operand
}
}
set.add(POINTER_ARROW + sym.getName());
return true;
}
Address address = (Address) data.getValue();
if (address != null && address.getOffset() != 0) {
set.add(POINTER_ARROW + address);
}
return true;
}
private void handlePotentialPointer(Set<String> list, Program program, Address toAddress,
Data data) {
if (data.isDefined()) {
return;
}
// if no data is defined at the address, see if it is a pointer
SymbolTable symbolTable = program.getSymbolTable();
PseudoDisassembler dis = new PseudoDisassembler(program);
Address pointerAddress = dis.getIndirectAddr(toAddress);
if (!isGoodAddress(program, pointerAddress)) {
return;
}
Symbol symbol = symbolTable.getPrimarySymbol(pointerAddress);
if (symbol != null) {
list.add(POINTER_ARROW + symbol.getName());
}
else {
list.add(POINTER_ARROW + pointerAddress);
}
}
private boolean handleDirectFlow(Set<String> set, Reference reference, Program program,
Address toAddr) {
if (!showAutomaticFunctions) {
return false;
}
RefType type = reference.getReferenceType();
if (!type.isFlow()) {
return false;
}
if (type.isIndirect()) {
return false;
}
if (type.isCall()) {
boolean showName = reference.isMnemonicReference();
String signature = getFunctionSignature(program, toAddr, showName);
if (signature != null) {
set.add(signature);
}
}
return true;
}
private String getUndefinedPointer(CodeUnit cu) {
if (!(cu instanceof Data)) {
return null;
}
Data data = (Data) cu;
DataType dataType = data.getDataType();
if (!(dataType instanceof Undefined || dataType instanceof DefaultDataType)) {
return null;
}
Program program = cu.getProgram();
if (programIsEntireMemorySpace(program)) {
// this prevents the case where a program represents the entire address space
// in which everything looks like a pointer
return null;
}
int align = program.getLanguage().getInstructionAlignment();
Address codeUnitAddress = cu.getAddress();
long codeUnitOffset = codeUnitAddress.getOffset();
if ((codeUnitOffset % align) != 0) {
// not aligned
return null;
}
int pointerSize = program.getDefaultPointerSize();
long addrLong = 0;
Memory memory = program.getMemory();
try {
switch (pointerSize) {
case 4:
int addrInt = memory.getInt(codeUnitAddress);
addrLong = (addrInt & 0xffffffffL);
addrLong *= codeUnitAddress.getAddressSpace().getAddressableUnitSize();
break;
case 8:
addrLong = memory.getLong(codeUnitAddress);
break;
}
}
catch (MemoryAccessException e) {
// handled below
}
if (addrLong != 0) {
try {
Address potentialAddr = codeUnitAddress.getNewAddress(addrLong);
if (memory.contains(potentialAddr)) {
return "? -> " + potentialAddr.toString();
}
}
catch (AddressOutOfBoundsException e) {
// ignore
}
}
return null;
}
private boolean programIsEntireMemorySpace(Program program) {
Address minAddress = program.getMinAddress();
Address maxAddress = program.getMaxAddress();
AddressSpace addressSpace = maxAddress.getAddressSpace();
Address spaceMaxAddress = addressSpace.getMaxAddress();
long minOffset = minAddress.getOffset();
if (minOffset == 0 && maxAddress.equals(spaceMaxAddress)) {
return true;
}
return false;
}
private String getFunctionSignature(Program program, Address funcAddr,
boolean displayFuncName) {
Function function = program.getFunctionManager().getFunctionAt(funcAddr);
if (function == null) {
return null;
}
return function.getPrototypeString(false, false);
}
/**
* Check if this address could really be a good address in the program.
* Never accept 0 as a valid address.
*
* @param program program to check if address is valid within.
* @param addr address in program to be checked
* @return true if this is a valid address
*/
private boolean isGoodAddress(Program program, Address addr) {
if (addr == null) {
return false;
}
if (!program.getMemory().contains(addr)) {
return false;
}
long offset = addr.getOffset();
if (offset == 0x0 || offset == 0xffffffff || offset == 0xffff || offset == 0xff) {
return false;
}
return true;
}
/**
* Gets an array of objects that indicate the repeatable comments at the "to addresses" of the
* references.
* @param listing the program listing
* @param memRefs the references whose repeatable comments we are interested in.
* @param showAll true indicates to show all referenced repeatable comments and not just the
* primary reference's repeatable comment.
* @return an array of objects, where each object is a RefRepeatComment containing an
* address and a String array of the repeatable comments for a reference.
*/
private RefRepeatComment[] getRepeatableComments(Listing listing, Reference[] memRefs,
boolean showAll) {
Set<RefRepeatComment> set = new LinkedHashSet<>();
for (int i = 0; i < memRefs.length && totalCommentsFound < maxDisplayLines; ++i) {
if (!showAll && !memRefs[i].isPrimary()) {
continue;
}
Address address = memRefs[i].getToAddress();
String[] comment = getComment(listing, address);
if (comment != null && comment.length > 0) {
set.add(new RefRepeatComment(address, comment));
totalCommentsFound++;
}
}
return set.toArray(new RefRepeatComment[set.size()]);
}
private String[] getComment(Listing listing, Address address) {
// prefer listing comments first since there may not be a code unit at this address
String repeatableComment = listing.getComment(CodeUnit.REPEATABLE_COMMENT, address);
if (repeatableComment != null) {
return StringUtilities.toLines(repeatableComment);
}
CodeUnit cu = listing.getCodeUnitAt(address);
if (cu == null) {
return null;
}
Function func = listing.getFunctionAt(address);
if (func != null) {
return func.getRepeatableCommentAsArray();
}
return cu.getCommentAsArray(CodeUnit.REPEATABLE_COMMENT);
}
/**
* Return all the comments
* @return the comments
*/
public String[] getComments() {
ArrayList<String> list = new ArrayList<>();
boolean hasEol = hasEOL();
boolean hasRepeatable = hasRepeatable();
boolean hasRefRepeats = hasReferencedRepeatable();
list.addAll(Arrays.asList((String[]) displayCommentArrays[MY_EOLS]));
if (alwaysShowRepeatable || !hasEol) {
list.addAll(Arrays.asList((String[]) displayCommentArrays[MY_REPEATABLES]));
}
if (alwaysShowRefRepeats || !(hasEol || hasRepeatable)) {
RefRepeatComment[] refRepeatComments =
(RefRepeatComment[]) displayCommentArrays[REF_REPEATABLES];
for (RefRepeatComment refRepeatComment : refRepeatComments) {
// Address addr = refRepeatComments[j].getAddress();
list.addAll(Arrays.asList(refRepeatComment.getCommentLines()));
}
}
if (alwaysShowAutomatic || !(hasEol || hasRepeatable || hasRefRepeats)) {
list.addAll(Arrays.asList((String[]) displayCommentArrays[MY_AUTOMATIC]));
}
return list.toArray(new String[list.size()]);
}
/**
* Gets the end of line comment as an array.
*
* @return the EOL comment
*/
public String[] getEOLComments() {
return (String[]) displayCommentArrays[MY_EOLS];
}
/**
* Gets the repeatable comment as an array.
* @return the repeatable comment.
*/
public String[] getRepeatableComments() {
return (String[]) displayCommentArrays[MY_REPEATABLES];
}
/**
* Gets the number of repeatable comments at the "to reference"s
* @return the number of reference repeatable comments
*/
public int getReferencedRepeatableCommentsCount() {
return displayCommentArrays[REF_REPEATABLES].length;
}
public String[] getReferencedRepeatableComments() {
ArrayList<String> stringList = new ArrayList<>();
int refRepeatCount = getReferencedRepeatableCommentsCount();
for (int i = 0; i < refRepeatCount; i++) {
RefRepeatComment refRepeatComment = getReferencedRepeatableComments(i);
String[] refRepeatComments = refRepeatComment.getCommentLines();
stringList.addAll(Arrays.asList(refRepeatComments));
}
return stringList.toArray(new String[stringList.size()]);
}
/**
* Gets a referenced repeatable comment as a RefRepeatComment object.
* @param index indicator of which referenced repeatable comment is desired.
* The value is 0 thru one less than the number of referenced repeatable comments.
* @return the RefRepeatComment containing the referenced address and its referenced repeatable comment
*/
public RefRepeatComment getReferencedRepeatableComments(int index) {
return (RefRepeatComment) displayCommentArrays[REF_REPEATABLES][index];
}
/**
* Gets a referenced repeatable comment as a RefRepeatComment object.
* @param refAddress the reference address whose repeatable comment is desired.
* Note: there must be a reference from the address for this displayableEol to the refAddress.
* @return the comment lines for the referenced address's repeatable comment or null.
*/
public String[] getReferencedRepeatableComments(Address refAddress) {
Object[] refRepeatArray = displayCommentArrays[REF_REPEATABLES];
for (Object element : refRepeatArray) {
RefRepeatComment refRepeatComment = (RefRepeatComment) element;
if (refRepeatComment.getAddress().equals(refAddress)) {
return refRepeatComment.getCommentLines();
}
}
return null;
}
/**
* Gets the automatic comment as an array.
* @return the automatic comment
*/
public String[] getAutomaticComment() {
return (String[]) displayCommentArrays[MY_AUTOMATIC];
}
@Override
public String toString() {
StringBuilder buffy = new StringBuilder();
String[] eols = (String[]) displayCommentArrays[MY_EOLS];
if (eols.length != 0) {
buffy.append("EOLs: ").append(Arrays.toString(eols));
}
String[] myRepeatables = (String[]) displayCommentArrays[MY_REPEATABLES];
if (myRepeatables.length != 0) {
buffy.append("My Repeatables: ").append(Arrays.toString(myRepeatables));
}
Object[] refRepeatables = displayCommentArrays[REF_REPEATABLES];
if (refRepeatables.length != 0) {
buffy.append("Ref Repeatables: ").append(Arrays.toString(refRepeatables));
}
String[] myAutomatic = (String[]) displayCommentArrays[MY_AUTOMATIC];
if (myAutomatic.length != 0) {
buffy.append("My Automatic: ").append(Arrays.toString(myAutomatic));
}
return buffy.toString();
}
public int getCommentLineCount(int subType) {
switch (subType) {
case MY_EOLS:
return ((String[]) displayCommentArrays[MY_EOLS]).length;
case MY_REPEATABLES:
return ((String[]) displayCommentArrays[MY_REPEATABLES]).length;
case REF_REPEATABLES:
int count = 0;
Object[] refRepeatArray = displayCommentArrays[REF_REPEATABLES];
for (Object element : refRepeatArray) {
count += ((RefRepeatComment) element).getCommentLines().length;
}
return count;
case MY_AUTOMATIC:
return ((String[]) displayCommentArrays[MY_AUTOMATIC]).length;
default:
throw new IllegalArgumentException(
subType + " is not a valid Eol Comment subType indicator.");
}
}
public int getRefRepeatableCommentLineCount(Address refAddress) {
Object[] refRepeatArray = displayCommentArrays[REF_REPEATABLES];
for (Object element : refRepeatArray) {
RefRepeatComment refRepeatComment = (RefRepeatComment) element;
if (refRepeatComment.getAddress().equals(refAddress)) {
return refRepeatComment.getCommentLines().length;
}
}
return 0;
}
private int getEolRow(ProgramLocation loc) {
int numBefore = 0;
boolean hasEol = hasEOL();
boolean hasRepeatable = hasRepeatable();
boolean hasRefRepeats = hasReferencedRepeatable();
if (loc instanceof EolCommentFieldLocation) {
EolCommentFieldLocation commentLoc = (EolCommentFieldLocation) loc;
return numBefore + commentLoc.getCurrentCommentRow();
}
numBefore += getCommentLineCount(DisplayableEol.MY_EOLS);
if (loc instanceof RepeatableCommentFieldLocation) {
RepeatableCommentFieldLocation commentLoc = (RepeatableCommentFieldLocation) loc;
return numBefore + commentLoc.getCurrentCommentRow();
}
if (alwaysShowRepeatable || !hasEol) {
numBefore += getCommentLineCount(DisplayableEol.MY_REPEATABLES);
}
if (loc instanceof RefRepeatCommentFieldLocation) {
RefRepeatCommentFieldLocation commentLoc = (RefRepeatCommentFieldLocation) loc;
Address desiredAddress = commentLoc.getReferencedRepeatableAddress();
int startRowInRefRepeats = getCommentStartRow(desiredAddress);
int rowInComment =
(hasRefRepeatComment(desiredAddress)) ? commentLoc.getCurrentCommentRow() : 0;
return numBefore + startRowInRefRepeats + rowInComment;
}
if (alwaysShowRefRepeats || !(hasEol || hasRepeatable)) {
numBefore += getCommentLineCount(DisplayableEol.REF_REPEATABLES);
}
if (loc instanceof AutomaticCommentFieldLocation) {
AutomaticCommentFieldLocation commentLoc = (AutomaticCommentFieldLocation) loc;
return numBefore + commentLoc.getCurrentCommentRow();
}
if (alwaysShowAutomatic || !(hasEol || hasRepeatable || hasRefRepeats)) {
numBefore += getCommentLineCount(DisplayableEol.MY_AUTOMATIC);
}
return numBefore;
}
private boolean hasRefRepeatComment(Address desiredAddress) {
RefRepeatComment[] refRepeatComments =
(RefRepeatComment[]) displayCommentArrays[REF_REPEATABLES];
for (RefRepeatComment comment : refRepeatComments) {
Address checkAddress = comment.getAddress();
if (desiredAddress.equals(checkAddress)) {
return true;
}
}
return false;
}
public RowColLocation getRowCol(CommentFieldLocation cloc) {
int strOffset = cloc.getCharOffset();
if (cloc instanceof RefRepeatCommentFieldLocation) {
RefRepeatCommentFieldLocation commentLoc = (RefRepeatCommentFieldLocation) cloc;
Address desiredAddress = commentLoc.getReferencedRepeatableAddress();
if (!hasRefRepeatComment(desiredAddress)) {
strOffset = 0;
}
}
int eolRow = getEolRow(cloc);
return new RowColLocation(eolRow, strOffset);
}
public ProgramLocation getLocation(int eolRow, int eolColumn) {
boolean hasEol = hasEOL();
boolean hasRepeatable = hasRepeatable();
boolean hasRefRepeats = hasReferencedRepeatable();
int numEol = getCommentLineCount(MY_EOLS);
int numRepeatable = getCommentLineCount(MY_REPEATABLES);
int numRefRepeats = getCommentLineCount(REF_REPEATABLES);
int numAutomatic = getCommentLineCount(MY_AUTOMATIC);
int[] cpath = null;
if (codeUnit instanceof Data) {
cpath = ((Data) codeUnit).getComponentPath();
}
int beforeEol = 0;
int beforeRepeatable = beforeEol + numEol;
int beforeRefRepeats = beforeRepeatable;
if (alwaysShowRepeatable || !hasEol) {
beforeRefRepeats += numRepeatable;
}
int beforeAutomatic = beforeRefRepeats;
if (alwaysShowRefRepeats || !(hasEol || hasRepeatable)) {
beforeAutomatic += numRefRepeats;
}
int numTotal = beforeAutomatic;
if (alwaysShowAutomatic || !(hasEol || hasRepeatable || hasRefRepeats)) {
numTotal += numAutomatic;
}
if (eolRow < 0) {
return null;
}
Program program = codeUnit.getProgram();
if (eolRow < beforeRepeatable) {
return new EolCommentFieldLocation(program, codeUnit.getMinAddress(), cpath,
getComments(), eolRow, eolColumn, eolRow);
}
if (eolRow < beforeRefRepeats) {
return new RepeatableCommentFieldLocation(program, codeUnit.getMinAddress(), cpath,
getComments(), eolRow, eolColumn, eolRow - beforeRepeatable);
}
if (eolRow < beforeAutomatic) {
int rowInAllRefRepeats = eolRow - beforeRefRepeats;
return new RefRepeatCommentFieldLocation(program, codeUnit.getMinAddress(), cpath,
getComments(), eolRow, eolColumn, getRefRepeatRow(rowInAllRefRepeats),
getRefRepeatAddress(rowInAllRefRepeats));
}
if (eolRow < numTotal) {
return new AutomaticCommentFieldLocation(program, codeUnit.getMinAddress(), cpath,
getComments(), eolRow, eolColumn, eolRow - beforeAutomatic);
}
return null;
}
private Address getRefRepeatAddress(int rowInAllRefRepeats) {
RefRepeatComment[] refRepeatComments =
(RefRepeatComment[]) displayCommentArrays[REF_REPEATABLES];
int currentStartRow = 0;
for (RefRepeatComment comment : refRepeatComments) {
int numRows = comment.getCommentLineCount();
if (rowInAllRefRepeats < (currentStartRow + numRows)) {
return comment.getAddress();
}
currentStartRow += numRows;
}
return null;
}
private int getRefRepeatRow(int rowInAllRefRepeats) {
RefRepeatComment[] refRepeatComments =
(RefRepeatComment[]) displayCommentArrays[REF_REPEATABLES];
int currentStartRow = 0;
for (RefRepeatComment comment : refRepeatComments) {
int numRows = comment.getCommentLineCount();
if (rowInAllRefRepeats < (currentStartRow + numRows)) {
return rowInAllRefRepeats - currentStartRow;
}
currentStartRow += numRows;
}
return -1;
}
private int getCommentStartRow(Address refAddress) {
RefRepeatComment[] refRepeatComments =
(RefRepeatComment[]) displayCommentArrays[REF_REPEATABLES];
int currentStartRow = 0;
for (RefRepeatComment comment : refRepeatComments) {
Address checkAddress = comment.getAddress();
if (refAddress.compareTo(checkAddress) <= 0) {
return currentStartRow;
}
currentStartRow += comment.getCommentLineCount();
}
return currentStartRow;
}
public boolean isRefRepeatRow(int eolRow) {
boolean hasEol = hasEOL();
boolean hasRepeatable = hasRepeatable();
int numEol = getCommentLineCount(MY_EOLS);
int numRepeatable = getCommentLineCount(MY_REPEATABLES);
int numRefRepeats = getCommentLineCount(REF_REPEATABLES);
int beforeEol = 0;
int beforeRepeatable = beforeEol + numEol;
int beforeRefRepeats = beforeRepeatable;
if (alwaysShowRepeatable || !hasEol) {
beforeRefRepeats += numRepeatable;
}
int beforeAutomatic = beforeRefRepeats;
if (alwaysShowRefRepeats || !(hasEol || hasRepeatable)) {
beforeAutomatic += numRefRepeats;
}
return ((eolRow >= beforeRefRepeats) && (eolRow < beforeAutomatic));
}
}

View File

@ -0,0 +1,819 @@
/* ###
* 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;
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.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.StringUtilities;
import util.CollectionUtils;
/**
* Utility class with methods to get comment information that can be displayed in the end of line
* comment field. Each instance of this class is associated with a code unit. This class uses the
* provided options to decide how to load and filter existing comments.
*
* <p>Comment types that can be shown include the End of Line comment for the code unit, the
* 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.
*/
public class EolComments {
private static final String POINTER_ARROW = "-> ";
private CodeUnit codeUnit;
private List<String> eols = new ArrayList<>();
private List<String> repeatables = new ArrayList<>();
private List<RefRepeatComment> refRepeatables = new ArrayList<>();
private List<String> autos = new ArrayList<>();
private List<Reference> references = 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
private boolean operandsShowReferences = false;
private int maxDisplayComments;
private EolExtraCommentsOption extraCommentsOption;
public EolComments(CodeUnit cu, boolean operandsShowReferences, int maxDisplayComments,
EolExtraCommentsOption extraCommentsOption) {
this.codeUnit = cu;
this.operandsShowReferences = operandsShowReferences;
this.maxDisplayComments = maxDisplayComments;
this.extraCommentsOption = extraCommentsOption;
loadComments();
}
private void loadComments() {
loadEols();
loadRepeatables();
loadRefRepeatables();
loadAutos();
}
/**
* Returns the current number of comments in this class. The value of this method will change
* as this class is loading comments. After loading, this value will be fixed.
* @return the size
*/
private int size() {
int refRepeatablesSize = 0;
for (RefRepeatComment item : refRepeatables) {
refRepeatablesSize += item.getCommentLineCount();
}
return eols.size() + repeatables.size() + refRepeatablesSize + autos.size();
}
/**
* Returns the number of comments that can be added before reaching the maximum number of
* comments
* @return the number of comments
*/
private int getAvailableSpace() {
return maxDisplayComments - size();
}
private void loadEols() {
Collection<String> comments =
Arrays.asList(codeUnit.getCommentAsArray(CodeUnit.EOL_COMMENT));
addStrings(comments, eols);
}
private void loadRepeatables() {
boolean hasOtherComments = !eols.isEmpty();
if (!extraCommentsOption.isShowingRepeatables(hasOtherComments)) {
return;
}
Collection<String> comments =
Arrays.asList(codeUnit.getCommentAsArray(CodeUnit.REPEATABLE_COMMENT));
addStrings(comments, repeatables);
}
private void loadRefRepeatables() {
boolean hasOtherComments = !(eols.isEmpty() && repeatables.isEmpty());
if (!extraCommentsOption.isShowingRefRepeatables(hasOtherComments)) {
return;
}
Collection<RefRepeatComment> refRepeatableComments = getRepeatableComments(true);
addRefRepeatables(refRepeatableComments, refRepeatables);
}
private void loadAutos() {
boolean hasOtherComments =
!(eols.isEmpty() && repeatables.isEmpty() && refRepeatables.isEmpty());
if (!extraCommentsOption.isShowingAutoComments(hasOtherComments)) {
return;
}
Collection<String> comments = getReferencePreviews();
addStrings(comments, autos);
}
private void addRefRepeatables(Collection<RefRepeatComment> from,
Collection<RefRepeatComment> to) {
int space = getAvailableSpace();
int total = 0;
for (RefRepeatComment item : from) {
to.add(item);
total += item.getCommentLineCount();
if (total == space) {
return;
}
}
}
private void addStrings(Collection<String> from, Collection<String> to) {
int space = getAvailableSpace();
for (String item : from) {
to.add(item);
if (to.size() == space) {
return;
}
}
}
private void loadReferences() {
if (!references.isEmpty()) {
return; // already loaded
}
// arbitrary limit to prevent excessive consumption of resources
int space = getAvailableSpace();
int max = Math.min(100, space);
Program program = codeUnit.getProgram();
ReferenceManager referenceManager = program.getReferenceManager();
AddressSet addresses = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
AddressIterator it = referenceManager.getReferenceSourceIterator(addresses, true);
while (it.hasNext() && references.size() < max) {
Address fromAddress = it.next();
Reference[] refs = referenceManager.getReferencesFrom(fromAddress);
for (Reference r : refs) {
references.add(r);
}
}
Collections.sort(references);
}
public boolean isShowingRepeatables() {
return !repeatables.isEmpty();
}
public boolean isShowingRefRepeatables() {
return !refRepeatables.isEmpty();
}
public boolean isShowingAutoComments() {
return !autos.isEmpty();
}
private Collection<String> getReferencePreviews() {
loadReferences();
if (references.isEmpty()) {
return getPreviewForNoReferences();
}
int space = getAvailableSpace();
Program program = codeUnit.getProgram();
Set<String> set = new LinkedHashSet<>();
for (Reference reference : references) {
if (set.size() >= space) {
break;
}
if (!isValidReference(program, reference)) {
continue;
}
createAutoCommentFromReference(set, program, reference);
}
return set;
}
private Collection<String> getPreviewForNoReferences() {
Set<String> set = new HashSet<>();
String translatedString = getTranslatedString();
if (translatedString != null) {
set.add(translatedString);
return set;
}
String pointerText = getUndefinedPointer(codeUnit);
if (pointerText != null) {
set.add(pointerText);
return set;
}
return set;
}
private String getTranslatedString() {
if (codeUnit instanceof Data data && StringDataInstance.isString(data)) {
StringDataInstance sdi = StringDataInstance.getStringDataInstance(data);
if (sdi.hasTranslatedValue()) {
// show the translated value
return sdi.getStringRepresentation(sdi.isShowTranslation());
}
}
return null;
}
private boolean isValidReference(Program program, Reference reference) {
if (!reference.isMemoryReference()) {
return false;
}
Address toAddress = reference.getToAddress();
return isValidAddress(program, toAddress);
}
private void createAutoCommentFromReference(Set<String> results, Program program,
Reference reference) {
Address toAddress = reference.getToAddress();
if (createFunctionCallPreview(results, reference, program, toAddress)) {
return;
}
Data data = getData(program, toAddress);
if (data == null) {
return; // nothing there
}
if (createIndirectDataReferencePreview(results, reference, program, toAddress, data)) {
return;
}
handleDirectDataReferencePreview(results, toAddress, data);
}
private Data getData(Program program, Address toAddr) {
Data data = program.getListing().getDataAt(toAddr);
if (data == null) {
data = program.getListing().getDataContaining(toAddr);
}
return data;
}
private void handleDirectDataReferencePreview(Set<String> set, Address address, Data data) {
Object value = data.getValue();
if (value instanceof Scalar) {
Scalar scalar = (Scalar) value;
if (scalar.getSignedValue() == 0) {
return;
}
}
String dataRepresentation = getDataValueRepresentation(address, data);
if (!StringUtils.isBlank(dataRepresentation)) {
set.add("= " + dataRepresentation);
}
}
private String getDataValueRepresentation(Address dataAccessAddress, Data data) {
if (extraCommentsOption.useAbbreviatedComments()) {
if (isOffcut(dataAccessAddress, data)) {
return getOffcutString(dataAccessAddress, data);
}
}
return data.getDefaultValueRepresentation();
}
private boolean isOffcut(Address address, CodeUnit cu) {
if (cu == null) {
return false;
}
return !cu.getMinAddress().equals(address);
}
private String getOffcutString(Address offcutAddress, Data data) {
Address dataAddress = data.getMinAddress();
int diff = (int) offcutAddress.subtract(dataAddress);
DataType dt = data.getBaseDataType();
return getOffcutString(data, dataAddress, diff, dt);
}
private String getOffcutString(Data data, Address dataAddress, int diff, DataType dt) {
if (StringDataInstance.isString(data)) {
StringDataInstance string = StringDataInstance.getStringDataInstance(data);
string = string.getByteOffcut(diff);
return string.getStringRepresentation();
}
if (!data.hasStringValue()) {
return null;
}
int length = data.getLength();
if (diff >= length) {
// not sure if this can happen--just use the default
return data.getDefaultValueRepresentation();
}
DumbMemBufferImpl mb = new DumbMemBufferImpl(data.getMemory(), dataAddress.add(diff));
return dt.getRepresentation(mb, data, length - diff);
}
private boolean createIndirectDataReferencePreview(Set<String> set, Reference reference,
Program program, Address toAddress, Data data) {
RefType type = reference.getReferenceType();
if (!type.isIndirect()) {
return false;
}
if (createDefinedDataPointerPreview(set, program, reference, data)) {
return true;
}
createUndefinedPointerPreview(set, program, toAddress, data);
return true;
}
private boolean createDefinedDataPointerPreview(Set<String> set, Program program,
Reference reference, Data data) {
if (!data.isPointer()) {
return false;
}
SymbolTable symbolTable = program.getSymbolTable();
ReferenceManager referenceManager = program.getReferenceManager();
Reference pointerReference =
referenceManager.getPrimaryReferenceFrom(reference.getToAddress(), 0);
if (pointerReference != null) {
Symbol symbol = symbolTable.getPrimarySymbol(pointerReference.getToAddress());
if (operandIsShowingSymbolReference(symbol, reference)) {
return true; // already displayed by operand
}
set.add(POINTER_ARROW + symbol.getName());
return true;
}
Address address = (Address) data.getValue();
if (address != null && address.getOffset() != 0) {
set.add(POINTER_ARROW + address);
}
return true;
}
private boolean operandIsShowingSymbolReference(Symbol symbol, Reference reference) {
if (operandsShowReferences && reference.getOperandIndex() != CodeUnit.MNEMONIC) {
if (!symbol.isDynamic()) {
return true;
}
}
return false;
}
private void createUndefinedPointerPreview(Set<String> list, Program program, Address toAddress,
Data data) {
if (data.isDefined()) {
return;
}
// if no data is defined at the address, see if it is a pointer
SymbolTable symbolTable = program.getSymbolTable();
PseudoDisassembler dis = new PseudoDisassembler(program);
Address pointerAddress = dis.getIndirectAddr(toAddress);
if (!isValidAddress(program, pointerAddress)) {
return;
}
Symbol symbol = symbolTable.getPrimarySymbol(pointerAddress);
if (symbol != null) {
list.add(POINTER_ARROW + symbol.getName());
}
else {
list.add(POINTER_ARROW + pointerAddress);
}
}
private boolean createFunctionCallPreview(Set<String> set, Reference reference, Program program,
Address toAddress) {
if (extraCommentsOption.getAutoFunction() == EolEnablement.NEVER) {
return false;
}
RefType type = reference.getReferenceType();
if (!type.isFlow()) {
return false;
}
if (type.isIndirect()) {
return false;
}
if (type.isCall()) {
String signature = getFunctionSignature(program, toAddress);
if (signature != null) {
set.add(signature);
}
}
return true;
}
private String getUndefinedPointer(CodeUnit cu) {
if (!(cu instanceof Data)) {
return null;
}
Data data = (Data) cu;
DataType dataType = data.getDataType();
if (!(dataType instanceof Undefined || dataType instanceof DefaultDataType)) {
return null;
}
Program program = cu.getProgram();
if (isEntireMemorySpace(program)) {
// everything looks like a pointer when a program represents the entire address space
return null;
}
int align = program.getLanguage().getInstructionAlignment();
Address codeUnitAddress = cu.getAddress();
long codeUnitOffset = codeUnitAddress.getOffset();
if ((codeUnitOffset % align) != 0) {
return null; // not aligned
}
return createPointerString(program, codeUnitAddress);
}
private String createPointerString(Program program, Address codeUnitAddress) {
int pointerSize = program.getDefaultPointerSize();
long offset = 0;
Memory memory = program.getMemory();
try {
switch (pointerSize) {
case 4:
int addrInt = memory.getInt(codeUnitAddress);
offset = (addrInt & 0xffffffffL);
offset *= codeUnitAddress.getAddressSpace().getAddressableUnitSize();
break;
case 8:
offset = memory.getLong(codeUnitAddress);
break;
default:
return null;
}
Address potentialAddr = codeUnitAddress.getNewAddress(offset);
if (memory.contains(potentialAddr)) {
return "? -> " + potentialAddr.toString();
}
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
// handled below
}
return null;
}
private boolean isEntireMemorySpace(Program program) {
Address min = program.getMinAddress();
Address max = program.getMaxAddress();
AddressSpace space = max.getAddressSpace();
return min.getOffset() == 0 && max.equals(space.getMaxAddress());
}
private String getFunctionSignature(Program program, Address a) {
// Note: Users have complained the 'undefined' return type clutters the display. Update
// signature to omit return type if it is undefined.
Function f = program.getFunctionManager().getFunctionAt(a);
if (f != null) {
return f.getPrototypeString(false, false);
}
return null;
}
/**
* Check if this address could be a valid address in the program. 0 id not a valid address.
*
* @param program program to check if address is valid within
* @param address address in program to be checked
* @return true if this is a valid address
*/
private boolean isValidAddress(Program program, Address address) {
if (address == null) {
return false;
}
if (!program.getMemory().contains(address)) {
return false;
}
long offset = address.getOffset();
if (offset == 0x0 || offset == 0xffffffff || offset == 0xffff || offset == 0xff) {
return false;
}
return true;
}
private Collection<RefRepeatComment> getRepeatableComments(boolean showAll) {
loadReferences();
int space = getAvailableSpace();
Set<RefRepeatComment> set = new LinkedHashSet<>();
for (int i = 0; i < references.size() && set.size() < space; ++i) {
Reference reference = references.get(i);
if (!showAll && !reference.isPrimary()) {
continue;
}
Address address = reference.getToAddress();
String[] comment = getComment(address);
if (!CollectionUtils.isBlank(comment)) {
set.add(new RefRepeatComment(address, comment));
}
}
return set;
}
private String[] getComment(Address address) {
Program program = codeUnit.getProgram();
Listing listing = program.getListing();
// prefer listing comments first since there may not be a code unit at this address
String repeatable = listing.getComment(CodeUnit.REPEATABLE_COMMENT, address);
if (repeatable != null) {
return StringUtilities.toLines(repeatable);
}
CodeUnit cu = listing.getCodeUnitAt(address);
if (cu == null) {
return null;
}
Function f = listing.getFunctionAt(address);
if (f != null) {
return f.getRepeatableCommentAsArray();
}
return cu.getCommentAsArray(CodeUnit.REPEATABLE_COMMENT);
}
/**
* Return all comments loaded by this class
* @return the comments
*/
public List<String> getComments() {
List<String> list = new ArrayList<>();
list.addAll(eols);
list.addAll(repeatables);
for (RefRepeatComment comment : refRepeatables) {
list.addAll(Arrays.asList(comment.getCommentLines()));
}
list.addAll(autos);
return list;
}
private String[] getCommentsArray() {
List<String> comments = getComments();
return comments.toArray(new String[comments.size()]);
}
/**
* Gets the End of Line comments
* @return the comments
*/
public List<String> getEOLComments() {
return Collections.unmodifiableList(eols);
}
/**
* Gets the repeatable comments
* @return the comments
*/
public List<String> getRepeatableComments() {
return Collections.unmodifiableList(repeatables);
}
/**
* Gets the repeatable comments at the "to reference"s
* @return the comments
*/
public List<RefRepeatComment> getReferencedRepeatableComments() {
return Collections.unmodifiableList(refRepeatables);
}
/**
* Gets the automatic comments
* @return the comments
*/
public List<String> getAutomaticComment() {
return Collections.unmodifiableList(autos);
}
@Override
public String toString() {
StringBuilder buffy = new StringBuilder();
if (eols.isEmpty()) {
buffy.append("EOLs: ").append(eols);
}
if (!repeatables.isEmpty()) {
buffy.append("My Repeatables: ").append(repeatables);
}
if (!refRepeatables.isEmpty()) {
buffy.append("Ref Repeatables: ").append(refRepeatables);
}
if (!autos.isEmpty()) {
buffy.append("My Automatic: ").append(autos);
}
return buffy.toString();
}
private int getEolRow(ProgramLocation loc) {
int numBefore = 0;
if (loc instanceof EolCommentFieldLocation) {
EolCommentFieldLocation commentLoc = (EolCommentFieldLocation) loc;
return numBefore + commentLoc.getCurrentCommentRow();
}
numBefore += eols.size();
if (loc instanceof RepeatableCommentFieldLocation) {
RepeatableCommentFieldLocation commentLoc = (RepeatableCommentFieldLocation) loc;
return numBefore + commentLoc.getCurrentCommentRow();
}
numBefore += repeatables.size();
if (loc instanceof RefRepeatCommentFieldLocation) {
RefRepeatCommentFieldLocation commentLoc = (RefRepeatCommentFieldLocation) loc;
Address desiredAddress = commentLoc.getReferencedRepeatableAddress();
int startRowInRefRepeats = getCommentStartRow(desiredAddress);
int rowInComment =
(hasRefRepeatComment(desiredAddress)) ? commentLoc.getCurrentCommentRow() : 0;
return numBefore + startRowInRefRepeats + rowInComment;
}
numBefore += refRepeatables.size();
if (loc instanceof AutomaticCommentFieldLocation) {
AutomaticCommentFieldLocation commentLoc = (AutomaticCommentFieldLocation) loc;
return numBefore + commentLoc.getCurrentCommentRow();
}
numBefore += autos.size();
return numBefore;
}
private boolean hasRefRepeatComment(Address desiredAddress) {
for (RefRepeatComment comment : refRepeatables) {
Address checkAddress = comment.getAddress();
if (desiredAddress.equals(checkAddress)) {
return true;
}
}
return false;
}
public RowColLocation getRowCol(CommentFieldLocation cloc) {
int offset = cloc.getCharOffset();
if (cloc instanceof RefRepeatCommentFieldLocation) {
RefRepeatCommentFieldLocation commentLoc = (RefRepeatCommentFieldLocation) cloc;
Address desiredAddress = commentLoc.getReferencedRepeatableAddress();
if (!hasRefRepeatComment(desiredAddress)) {
offset = 0;
}
}
int eolRow = getEolRow(cloc);
return new RowColLocation(eolRow, offset);
}
public ProgramLocation getLocation(int eolRow, int eolColumn) {
if (eolRow < 0) {
return null;
}
int numEol = eols.size();
int numRepeatable = repeatables.size();
int numRefRepeats = refRepeatables.size();
int numAutomatic = autos.size();
int beforeRepeatable = numEol;
int beforeRefRepeats = beforeRepeatable;
if (!repeatables.isEmpty()) {
beforeRefRepeats += numRepeatable;
}
int beforeAutomatic = beforeRefRepeats;
if (!refRepeatables.isEmpty()) {
beforeAutomatic += numRefRepeats;
}
int numTotal = beforeAutomatic;
if (!autos.isEmpty()) {
numTotal += numAutomatic;
}
Program program = codeUnit.getProgram();
Address minAddress = codeUnit.getMinAddress();
int[] cpath = null;
if (codeUnit instanceof Data) {
cpath = ((Data) codeUnit).getComponentPath();
}
if (eolRow < beforeRepeatable) {
return new EolCommentFieldLocation(program, minAddress, cpath,
getCommentsArray(), eolRow, eolColumn, eolRow);
}
if (eolRow < beforeRefRepeats) {
return new RepeatableCommentFieldLocation(program, minAddress, cpath,
getCommentsArray(), eolRow, eolColumn, eolRow - beforeRepeatable);
}
if (eolRow < beforeAutomatic) {
int rowInAllRefRepeats = eolRow - beforeRefRepeats;
return new RefRepeatCommentFieldLocation(program, minAddress, cpath,
getCommentsArray(), eolRow, eolColumn, getRefRepeatRow(rowInAllRefRepeats),
getRefRepeatAddress(rowInAllRefRepeats));
}
if (eolRow < numTotal) {
return new AutomaticCommentFieldLocation(program, minAddress, cpath,
getCommentsArray(), eolRow, eolColumn, eolRow - beforeAutomatic);
}
return null;
}
private Address getRefRepeatAddress(int row) {
int currentRow = 0;
for (RefRepeatComment comment : refRepeatables) {
int lineCount = comment.getCommentLineCount();
if (row < (currentRow + lineCount)) {
return comment.getAddress();
}
currentRow += lineCount;
}
return null;
}
private int getRefRepeatRow(int row) {
int currentRow = 0;
for (RefRepeatComment comment : refRepeatables) {
int numRows = comment.getCommentLineCount();
if (row < (currentRow + numRows)) {
return row - currentRow;
}
currentRow += numRows;
}
return -1;
}
private int getCommentStartRow(Address address) {
int currentRow = 0;
for (RefRepeatComment comment : refRepeatables) {
Address commentAddress = comment.getAddress();
if (address.compareTo(commentAddress) <= 0) {
return currentRow;
}
currentRow += comment.getCommentLineCount();
}
return currentRow;
}
}

View File

@ -17,10 +17,12 @@ package ghidra.app.util.exporter;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.app.util.DisplayableEol;
import ghidra.app.util.EolComments;
import ghidra.app.util.template.TemplateSimplifier;
import ghidra.app.util.viewer.field.EolExtraCommentsOption;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
@ -56,10 +58,6 @@ class ProgramTextWriter {
ProgramTextOptions options, ServiceProvider provider) throws FileNotFoundException {
this.options = options;
// Exit if options are INVALID
int len = options.getAddrWidth() + options.getBytesWidth() + options.getPreMnemonicWidth() +
options.getMnemonicWidth() + options.getOperandWidth() + options.getEolWidth();
this.program = program;
this.listing = program.getListing();
this.memory = program.getMemory();
@ -293,29 +291,7 @@ class ProgramTextWriter {
//// End of Line Area //////////////////////////////////////////
if (options.isShowComments()) {
DisplayableEol displayableEol = new DisplayableEol(currentCodeUnit, false, false,
false, true, 6 /* arbitrary! */, true, true);
String[] eol = displayableEol.getComments();
if (eol != null && eol.length > 0) {
len = options.getAddrWidth() + options.getBytesWidth() +
options.getPreMnemonicWidth() + options.getMnemonicWidth() +
options.getOperandWidth();
String fill = genFill(len);
for (int i = 0; i < eol.length; ++i) {
if (i > 0) {
buffy.append(fill);
}
String eolcmt = options.getCommentPrefix() + eol[i];
if (eolcmt.length() > options.getEolWidth()) {
eolcmt = clip(eolcmt, options.getEolWidth(), true, true);
}
buffy.append(eolcmt);
writer.println(buffy.toString());
buffy = new StringBuilder();
}
}
addComments(currentCodeUnit);
}
if (buffy.length() > 0) {
@ -369,6 +345,35 @@ class ProgramTextWriter {
writer.close();
}
private void addComments(CodeUnit currentCodeUnit) {
EolExtraCommentsOption eolOption = new EolExtraCommentsOption();
EolComments eolComments =
new EolComments(currentCodeUnit, true, 6 /* arbitrary */, eolOption);
List<String> comments = eolComments.getComments();
if (comments.isEmpty()) {
return;
}
int len = options.getAddrWidth() + options.getBytesWidth() +
options.getPreMnemonicWidth() + options.getMnemonicWidth() +
options.getOperandWidth();
String fill = genFill(len);
for (int i = 0; i < comments.size(); ++i) {
if (i > 0) {
buffy.append(fill);
}
String text = options.getCommentPrefix() + comments.get(i);
if (text.length() > options.getEolWidth()) {
text = clip(text, options.getEolWidth(), true, true);
}
buffy.append(text);
writer.println(buffy.toString());
buffy = new StringBuilder();
}
}
private void insertUndefinedBytesRemovedMarker(Address bytesRemovedRangeStart,
Address bytesRemovedRangeEnd) {

View File

@ -16,6 +16,7 @@
package ghidra.app.util.viewer.field;
import java.awt.Color;
import java.beans.PropertyEditor;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
@ -28,57 +29,49 @@ import ghidra.app.util.viewer.field.ListingColors.CommentColors;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.options.OptionsGui;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.options.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.exception.AssertException;
/**
* Generates End of line comment Fields.
*/
* Generates End of line comment Fields.
*/
public class EolCommentFieldFactory extends FieldFactory {
public static final String FIELD_NAME = "EOL Comment";
private static final String GROUP_TITLE = "EOL Comments Field";
private static final String SEMICOLON_PREFIX = "; ";
public static final String ENABLE_WORD_WRAP_MSG =
public static final String ENABLE_WORD_WRAP_KEY =
GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME;
public static final String MAX_DISPLAY_LINES_MSG =
GROUP_TITLE + Options.DELIMITER + "Maximum Lines To Display";
public static final String ENABLE_SHOW_SEMICOLON_MSG =
GROUP_TITLE + Options.DELIMITER + "Show Semicolon at Start of Each Line";
public static final String ENABLE_ALWAYS_SHOW_REPEATABLE_MSG =
GROUP_TITLE + Options.DELIMITER + "Always Show the Repeatable Comment";
public static final String ENABLE_ALWAYS_SHOW_REF_REPEATABLE_MSG =
GROUP_TITLE + Options.DELIMITER + "Always Show the Referenced Repeatable Comments";
public static final String ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG =
GROUP_TITLE + Options.DELIMITER + "Always Show the Automatic Comment";
public static final String USE_ABBREVIATED_AUTOMITIC_COMMENT_MSG =
GROUP_TITLE + Options.DELIMITER + "Use Abbreviated Automatic Comments";
public static final String SHOW_FUNCTION_AUTOMITIC_COMMENT_MSG =
GROUP_TITLE + Options.DELIMITER + "Show Function Reference Automatic Comments";
public static final String ENABLE_PREPEND_REF_ADDRESS_MSG =
GROUP_TITLE + Options.DELIMITER + "Prepend the Address to Each Referenced Comment";
public static final String MAX_DISPLAY_LINES_KEY =
GROUP_TITLE + Options.DELIMITER + "Maximum Lines";
public static final String ENABLE_SHOW_SEMICOLON_KEY =
GROUP_TITLE + Options.DELIMITER + "Prepend Semicolon";
public static final String ENABLE_PREPEND_REF_ADDRESS_KEY =
GROUP_TITLE + Options.DELIMITER + "Prepend Address to References";
public static final String EXTRA_COMMENT_KEY =
GROUP_TITLE + Options.DELIMITER + "Auto Comments";
public static final Color DEFAULT_COLOR = Palette.BLUE;
private boolean isWordWrap;
private int maxDisplayLines;
private boolean showSemicolon;
private boolean alwaysShowRepeatable;
private boolean alwaysShowRefRepeatables;
private boolean alwaysShowAutomatic;
private boolean useAbbreviatedAutomatic;
private boolean showAutomaticFunctions;
private boolean prependRefAddress;
private int repeatableCommentStyle;
private int automaticCommentStyle;
private int refRepeatableCommentStyle;
// The codeUnitFormatOptions is used to monitor "follow pointer..." option to avoid
// duplication of data within auto-comment. We don't bother adding a listener
// to kick the model since this is done by the operand field.
private EolExtraCommentsOption extraCommentsOption = new EolExtraCommentsOption();
private PropertyEditor extraCommmentsEditor = new EolExtraCommentsPropertyEditor();
// The codeUnitFormatOptions is used to monitor "follow pointer..." option to avoid duplication
// of data within auto-comment. We don't bother adding a listener to kick the model since this
// is done by the operand field.
private BrowserCodeUnitFormatOptions codeUnitFormatOptions;
/**
@ -100,55 +93,49 @@ public class EolCommentFieldFactory extends FieldFactory {
super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions);
HelpLocation hl = new HelpLocation("CodeBrowserPlugin", "EOL_Comments_Field");
fieldOptions.registerOption(MAX_DISPLAY_LINES_MSG, 6, hl,
fieldOptions.registerOption(MAX_DISPLAY_LINES_KEY, 6, hl,
"The maximum number of lines used to display the end-of-line comment.");
fieldOptions.registerOption(ENABLE_WORD_WRAP_MSG, false, hl,
fieldOptions.registerOption(ENABLE_WORD_WRAP_KEY, false, hl,
FieldUtils.WORD_WRAP_OPTION_DESCRIPTION);
fieldOptions.registerOption(ENABLE_SHOW_SEMICOLON_MSG, false, hl,
fieldOptions.registerOption(ENABLE_SHOW_SEMICOLON_KEY, false, hl,
"Displays a semi-colon before each line in the end-of-line comment. " +
"This option is ignored if word wrapping is on.");
fieldOptions.registerOption(ENABLE_ALWAYS_SHOW_REPEATABLE_MSG, false, hl,
"Displays all referenced repeatable comments even if there is an EOL " +
"or repeatable comment at the code unit.");
fieldOptions.registerOption(ENABLE_ALWAYS_SHOW_REF_REPEATABLE_MSG, false, hl,
"Displays all referenced repeatable comments even if there is an EOL " +
"or repeatable comment at the code unit.");
fieldOptions.registerOption(ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG, false, hl,
"Displays an automatic comment whenever one exists instead of only if there " +
"aren't any EOL or repeatable comments.");
fieldOptions.registerOption(USE_ABBREVIATED_AUTOMITIC_COMMENT_MSG, true, hl,
"When showing automatic comments, show the smallest amount of information possible");
fieldOptions.registerOption(SHOW_FUNCTION_AUTOMITIC_COMMENT_MSG, true, hl,
"When showing automatic comments, show direct function references");
fieldOptions.registerOption(ENABLE_PREPEND_REF_ADDRESS_MSG, false, hl,
fieldOptions.registerOption(ENABLE_PREPEND_REF_ADDRESS_KEY, false, hl,
"Displays the address before each referenced repeatable comment.");
maxDisplayLines = fieldOptions.getInt(MAX_DISPLAY_LINES_MSG, 6);
isWordWrap = fieldOptions.getBoolean(ENABLE_WORD_WRAP_MSG, false);
maxDisplayLines = fieldOptions.getInt(MAX_DISPLAY_LINES_KEY, 6);
isWordWrap = fieldOptions.getBoolean(ENABLE_WORD_WRAP_KEY, false);
repeatableCommentStyle =
displayOptions.getInt(OptionsGui.COMMENT_REPEATABLE.getStyleOptionName(), -1);
automaticCommentStyle =
displayOptions.getInt(OptionsGui.COMMENT_AUTO.getStyleOptionName(), -1);
refRepeatableCommentStyle =
displayOptions.getInt(OptionsGui.COMMENT_REF_REPEAT.getStyleOptionName(), -1);
showSemicolon = fieldOptions.getBoolean(ENABLE_SHOW_SEMICOLON_MSG, false);
alwaysShowRepeatable = fieldOptions.getBoolean(ENABLE_ALWAYS_SHOW_REPEATABLE_MSG, false);
alwaysShowRefRepeatables =
fieldOptions.getBoolean(ENABLE_ALWAYS_SHOW_REF_REPEATABLE_MSG, false);
alwaysShowAutomatic = fieldOptions.getBoolean(ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG, false);
useAbbreviatedAutomatic =
fieldOptions.getBoolean(USE_ABBREVIATED_AUTOMITIC_COMMENT_MSG, true);
showAutomaticFunctions = fieldOptions.getBoolean(SHOW_FUNCTION_AUTOMITIC_COMMENT_MSG, true);
prependRefAddress = fieldOptions.getBoolean(ENABLE_PREPEND_REF_ADDRESS_MSG, false);
showSemicolon = fieldOptions.getBoolean(ENABLE_SHOW_SEMICOLON_KEY, false);
prependRefAddress = fieldOptions.getBoolean(ENABLE_PREPEND_REF_ADDRESS_KEY, false);
fieldOptions.getOptions(GROUP_TITLE).setOptionsHelpLocation(hl);
codeUnitFormatOptions = new BrowserCodeUnitFormatOptions(fieldOptions, true);
setupAutoCommentOptions(fieldOptions, hl);
}
private void setupAutoCommentOptions(Options fieldOptions, HelpLocation hl) {
fieldOptions.registerOption(EXTRA_COMMENT_KEY, OptionType.CUSTOM_TYPE,
new EolExtraCommentsOption(), hl, "The group of auto comment options",
extraCommmentsEditor);
CustomOption customOption = fieldOptions.getCustomOption(EXTRA_COMMENT_KEY, null);
if (!(customOption instanceof EolExtraCommentsOption)) {
throw new AssertException("Someone set an option for " + EXTRA_COMMENT_KEY +
" that is not the expected " +
EolExtraCommentsOption.class.getName() + " type.");
}
extraCommentsOption = (EolExtraCommentsOption) customOption;
}
/**
@ -161,28 +148,19 @@ public class EolCommentFieldFactory extends FieldFactory {
@Override
public void fieldOptionsChanged(Options options, String optionName, Object oldValue,
Object newValue) {
if (optionName.equals(MAX_DISPLAY_LINES_MSG)) {
if (optionName.equals(MAX_DISPLAY_LINES_KEY)) {
setMaximumLinesToDisplay(((Integer) newValue).intValue(), options);
}
else if (optionName.equals(ENABLE_WORD_WRAP_MSG)) {
else if (optionName.equals(ENABLE_WORD_WRAP_KEY)) {
isWordWrap = ((Boolean) newValue).booleanValue();
}
else if (optionName.equals(ENABLE_SHOW_SEMICOLON_MSG)) {
else if (optionName.equals(ENABLE_SHOW_SEMICOLON_KEY)) {
showSemicolon = ((Boolean) newValue).booleanValue();
}
else if (optionName.equals(ENABLE_ALWAYS_SHOW_REPEATABLE_MSG)) {
alwaysShowRepeatable = ((Boolean) newValue).booleanValue();
else if (optionName.equals(EXTRA_COMMENT_KEY)) {
extraCommentsOption = (EolExtraCommentsOption) newValue;
}
else if (optionName.equals(ENABLE_ALWAYS_SHOW_REF_REPEATABLE_MSG)) {
alwaysShowRefRepeatables = ((Boolean) newValue).booleanValue();
}
else if (optionName.equals(ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG)) {
alwaysShowAutomatic = ((Boolean) newValue).booleanValue();
}
else if (optionName.equals(USE_ABBREVIATED_AUTOMITIC_COMMENT_MSG)) {
useAbbreviatedAutomatic = ((Boolean) newValue).booleanValue();
}
else if (optionName.equals(ENABLE_PREPEND_REF_ADDRESS_MSG)) {
else if (optionName.equals(ENABLE_PREPEND_REF_ADDRESS_KEY)) {
prependRefAddress = ((Boolean) newValue).booleanValue();
}
}
@ -249,7 +227,7 @@ public class EolCommentFieldFactory extends FieldFactory {
private void setMaximumLinesToDisplay(int maxLines, Options options) {
if (maxLines < 1) {
maxLines = 1;
options.setInt(MAX_DISPLAY_LINES_MSG, maxLines);
options.setInt(MAX_DISPLAY_LINES_KEY, maxLines);
}
maxDisplayLines = maxLines;
}
@ -279,57 +257,44 @@ public class EolCommentFieldFactory extends FieldFactory {
}
}
DisplayableEol displayableEol =
new DisplayableEol(cu, alwaysShowRepeatable, alwaysShowRefRepeatables,
alwaysShowAutomatic, codeUnitFormatOptions.followReferencedPointers(),
maxDisplayLines, useAbbreviatedAutomatic, showAutomaticFunctions);
List<FieldElement> elementList = new ArrayList<>();
EolComments comments =
new EolComments(cu, codeUnitFormatOptions.followReferencedPointers(),
maxDisplayLines, extraCommentsOption);
// This Code Unit's End of Line Comment
AttributedString myEolPrefixString = new AttributedString(SEMICOLON_PREFIX,
CommentColors.EOL, getMetrics(style), false, null);
String[] eolComments = displayableEol.getEOLComments();
List<FieldElement> eolFieldElements = convertToFieldElements(program, eolComments,
myEolPrefixString, showSemicolon, isWordWrap, getNextRow(elementList));
elementList.addAll(eolFieldElements);
List<FieldElement> elementList = new ArrayList<>();
AttributedString prefix = createPrefix(style);
List<String> eols = comments.getEOLComments();
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0);
elementList.addAll(eolElements);
// This Code Unit's Repeatable Comment
if (alwaysShowRepeatable || elementList.isEmpty()) {
AttributedString myRepeatablePrefixString = new AttributedString(SEMICOLON_PREFIX,
CommentColors.REPEATABLE, getMetrics(repeatableCommentStyle), false, null);
String[] repeatableComments = displayableEol.getRepeatableComments();
List<FieldElement> repeatableFieldElements =
convertToFieldElements(program, repeatableComments, myRepeatablePrefixString,
showSemicolon, isWordWrap, getNextRow(elementList));
elementList.addAll(repeatableFieldElements);
if (comments.isShowingRepeatables()) {
prefix = createPrefix(repeatableCommentStyle);
int row = getNextRow(elementList);
List<String> repeatables = comments.getRepeatableComments();
List<FieldElement> elements = convertToFieldElements(program, repeatables, prefix, row);
elementList.addAll(elements);
}
// Referenced Repeatable Comments
if (alwaysShowRefRepeatables || elementList.isEmpty()) {
AttributedString refRepeatPrefixString = new AttributedString(SEMICOLON_PREFIX,
CommentColors.REF_REPEATABLE, getMetrics(refRepeatableCommentStyle), false, null);
int refRepeatCount = displayableEol.getReferencedRepeatableCommentsCount();
for (int subTypeIndex = 0; subTypeIndex < refRepeatCount; subTypeIndex++) {
RefRepeatComment refRepeatComment =
displayableEol.getReferencedRepeatableComments(subTypeIndex);
String[] refRepeatComments = refRepeatComment.getCommentLines();
List<FieldElement> refRepeatFieldElements = convertToRefFieldElements(
refRepeatComments, program, refRepeatPrefixString, showSemicolon, isWordWrap,
prependRefAddress, refRepeatComment.getAddress(), getNextRow(elementList));
elementList.addAll(refRepeatFieldElements);
if (comments.isShowingRefRepeatables()) {
prefix = createPrefix(refRepeatableCommentStyle);
List<RefRepeatComment> refRepeatables =
comments.getReferencedRepeatableComments();
for (RefRepeatComment comment : refRepeatables) {
int row = getNextRow(elementList);
String[] lines = comment.getCommentLines();
List<FieldElement> elements = convertToRefFieldElements(
lines, program, prefix, comment.getAddress(), row);
elementList.addAll(elements);
}
}
// Automatic Comment
if (alwaysShowAutomatic || elementList.isEmpty()) {
AttributedString autoCommentPrefixString = new AttributedString(SEMICOLON_PREFIX,
CommentColors.AUTO, getMetrics(automaticCommentStyle), false, null);
String[] autoComment = displayableEol.getAutomaticComment();
List<FieldElement> autoCommentFieldElements =
convertToFieldElements(program, autoComment, autoCommentPrefixString, showSemicolon,
isWordWrap, getNextRow(elementList));
elementList.addAll(autoCommentFieldElements);
if (comments.isShowingAutoComments()) {
prefix = createPrefix(automaticCommentStyle);
int row = getNextRow(elementList);
List<String> autos = comments.getAutomaticComment();
List<FieldElement> elements = convertToFieldElements(program, autos, prefix, row);
elementList.addAll(elements);
}
FieldElement[] fieldElements = elementList.toArray(new FieldElement[elementList.size()]);
@ -340,6 +305,27 @@ public class EolCommentFieldFactory extends FieldFactory {
maxDisplayLines, hlProvider);
}
private AttributedString createPrefix(int commentStyle) {
if (commentStyle == style) {
return new AttributedString(SEMICOLON_PREFIX, CommentColors.EOL, getMetrics(style),
false,
null);
}
if (commentStyle == repeatableCommentStyle) {
return new AttributedString(SEMICOLON_PREFIX,
CommentColors.REPEATABLE, getMetrics(repeatableCommentStyle), false, null);
}
if (commentStyle == refRepeatableCommentStyle) {
return new AttributedString(SEMICOLON_PREFIX,
CommentColors.REF_REPEATABLE, getMetrics(refRepeatableCommentStyle), false, null);
}
if (commentStyle == automaticCommentStyle) {
return new AttributedString(SEMICOLON_PREFIX,
CommentColors.AUTO, getMetrics(automaticCommentStyle), false, null);
}
throw new AssertException("Unexected comment style: " + commentStyle);
}
private int getNextRow(List<FieldElement> elementList) {
int elementIndex = elementList.size() - 1;
if (elementIndex >= 0) {
@ -352,26 +338,25 @@ public class EolCommentFieldFactory extends FieldFactory {
return 0;
}
private List<FieldElement> convertToFieldElements(Program program, String[] comments,
AttributedString currentPrefixString, boolean showPrefix, boolean wordWrap,
int nextRow) {
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
AttributedString currentPrefixString, int nextRow) {
List<FieldElement> fieldElements = new ArrayList<>();
if (comments.length == 0) {
if (comments.isEmpty()) {
return fieldElements;
}
for (int rowIndex = 0; rowIndex < comments.length; rowIndex++) {
for (int rowIndex = 0; rowIndex < comments.size(); rowIndex++) {
int encodedRow = nextRow + rowIndex;
fieldElements.add(CommentUtils.parseTextForAnnotations(comments[rowIndex], program,
fieldElements.add(CommentUtils.parseTextForAnnotations(comments.get(rowIndex), program,
currentPrefixString, encodedRow));
}
if (wordWrap) {
int lineWidth = showPrefix ? width - currentPrefixString.getStringWidth() : width;
if (isWordWrap) {
int lineWidth = showSemicolon ? width - currentPrefixString.getStringWidth() : width;
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
}
if (showPrefix) {
if (showSemicolon) {
for (int i = 0; i < fieldElements.size(); i++) {
RowColLocation startRowCol =
fieldElements.get(i).getDataLocationForCharacterIndex(0);
@ -387,8 +372,7 @@ public class EolCommentFieldFactory extends FieldFactory {
}
private List<FieldElement> convertToRefFieldElements(String[] comments, Program program,
AttributedString currentPrefixString, boolean showPrefix, boolean wordWrap,
boolean showRefAddress, Address refAddress, int nextRow) {
AttributedString currentPrefixString, Address refAddress, int nextRow) {
int numCommentLines = comments.length;
List<FieldElement> fieldElements = new ArrayList<>();
@ -400,7 +384,7 @@ public class EolCommentFieldFactory extends FieldFactory {
fieldElements.add(CommentUtils.parseTextForAnnotations(comments[rowIndex], program,
currentPrefixString, encodedRow));
}
if (showRefAddress) {
if (prependRefAddress) {
FieldElement commentElement = fieldElements.get(0);
// Address
String refAddrComment = "{@address " + refAddress.toString() + "}";
@ -418,12 +402,12 @@ public class EolCommentFieldFactory extends FieldFactory {
new FieldElement[] { addressElement, spacerElement, commentElement }));
}
if (wordWrap) {
int lineWidth = showPrefix ? width - currentPrefixString.getStringWidth() : width;
if (isWordWrap) {
int lineWidth = showSemicolon ? width - currentPrefixString.getStringWidth() : width;
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
}
if (showPrefix) {
if (showSemicolon) {
for (int i = 0; i < fieldElements.size(); i++) {
RowColLocation startRowCol =
fieldElements.get(i).getDataLocationForCharacterIndex(0);
@ -451,10 +435,9 @@ public class EolCommentFieldFactory extends FieldFactory {
return null;
}
CodeUnit cu = (CodeUnit) obj;
DisplayableEol displayableEol =
new DisplayableEol(cu, alwaysShowRepeatable, alwaysShowRefRepeatables,
alwaysShowAutomatic, codeUnitFormatOptions.followReferencedPointers(),
maxDisplayLines, useAbbreviatedAutomatic, showAutomaticFunctions);
EolComments displayableEol =
new EolComments(cu, codeUnitFormatOptions.followReferencedPointers(),
maxDisplayLines, extraCommentsOption);
// Hold position in connected tool if navigating within semicolon.
int numLeadColumns = 0;
@ -489,10 +472,9 @@ public class EolCommentFieldFactory extends FieldFactory {
return null;
}
DisplayableEol displayableEol =
new DisplayableEol((CodeUnit) obj, alwaysShowRepeatable, alwaysShowRefRepeatables,
alwaysShowAutomatic, codeUnitFormatOptions.followReferencedPointers(),
maxDisplayLines, useAbbreviatedAutomatic, showAutomaticFunctions);
EolComments displayableEol =
new EolComments((CodeUnit) obj, codeUnitFormatOptions.followReferencedPointers(),
maxDisplayLines, extraCommentsOption);
ListingTextField btf = (ListingTextField) bf;
RowColLocation eolRowCol = displayableEol.getRowCol((CommentFieldLocation) loc);

View File

@ -0,0 +1,22 @@
/* ###
* 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;
public enum EolEnablement {
ALWAYS,
DEFAULT,
NEVER;
}

View File

@ -0,0 +1,175 @@
/* ###
* 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 ghidra.framework.options.CustomOption;
import ghidra.framework.options.GProperties;
/**
* An option class that is used by the {@link EolExtraCommentsPropertyEditor} to load and save
* option settings.
*/
public class EolExtraCommentsOption implements CustomOption {
private static final String BASE_KEY = "extraComment";
private static final String KEY_REPETABLE = BASE_KEY + "Repeatable";
private static final String KEY_REF_REPETABLE = BASE_KEY + "RefRepeatable";
private static final String KEY_AUTO_DATA = BASE_KEY + "AutoData";
private static final String KEY_AUTO_FUNCTION = BASE_KEY + "AutoFunction";
private static final String KEY_USE_ABBRAVIATED = BASE_KEY + "UseAbbreviated";
private EolEnablement repeatable = EolEnablement.DEFAULT;
private EolEnablement refRepeatable = EolEnablement.DEFAULT;
private EolEnablement autoFunction = EolEnablement.DEFAULT;
private EolEnablement autoData = EolEnablement.DEFAULT;
private boolean useAbbreviatedComments = true;
public EolExtraCommentsOption() {
// required for persistence
}
public EolEnablement getRepeatable() {
return repeatable;
}
public void setRepeatable(EolEnablement priority) {
repeatable = priority;
}
public EolEnablement getRefRepeatable() {
return refRepeatable;
}
public void setRefRepeatable(EolEnablement priority) {
refRepeatable = priority;
}
public EolEnablement getAutoData() {
return autoData;
}
public void setAutoData(EolEnablement priority) {
autoData = priority;
}
public EolEnablement getAutoFunction() {
return autoFunction;
}
public void setAutoFunction(EolEnablement priority) {
autoFunction = priority;
}
public boolean useAbbreviatedComments() {
return useAbbreviatedComments;
}
public void setUseAbbreviatedComments(boolean b) {
useAbbreviatedComments = b;
}
public boolean alwaysShowAutoComments() {
return autoData == EolEnablement.ALWAYS || autoFunction == EolEnablement.ALWAYS;
}
public boolean isShowingRefRepeatables(boolean hasOtherComments) {
return isShowing(refRepeatable, hasOtherComments);
}
public boolean isShowingRepeatables(boolean hasOtherComments) {
return isShowing(repeatable, hasOtherComments);
}
public boolean isShowingAutoComments(boolean hasOtherComments) {
if (alwaysShowAutoComments()) {
return true;
}
if (isShowing(autoData, hasOtherComments)) {
return true;
}
return isShowing(autoFunction, hasOtherComments);
}
private boolean isShowing(EolEnablement enablement, boolean hasExistingComments) {
return enablement == EolEnablement.ALWAYS ||
(enablement == EolEnablement.DEFAULT && !hasExistingComments);
}
@Override
public void readState(GProperties properties) {
repeatable = properties.getEnum(KEY_REPETABLE, EolEnablement.DEFAULT);
refRepeatable = properties.getEnum(KEY_REF_REPETABLE, EolEnablement.DEFAULT);
autoData = properties.getEnum(KEY_AUTO_DATA, EolEnablement.DEFAULT);
autoFunction = properties.getEnum(KEY_AUTO_FUNCTION, EolEnablement.DEFAULT);
useAbbreviatedComments = properties.getBoolean(KEY_USE_ABBRAVIATED, true);
}
@Override
public void writeState(GProperties properties) {
properties.putEnum(KEY_REPETABLE, repeatable);
properties.putEnum(KEY_REF_REPETABLE, refRepeatable);
properties.putEnum(KEY_AUTO_DATA, autoData);
properties.putEnum(KEY_AUTO_FUNCTION, autoFunction);
properties.putBoolean(KEY_USE_ABBRAVIATED, useAbbreviatedComments);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((autoData == null) ? 0 : autoData.hashCode());
result = prime * result + ((autoFunction == null) ? 0 : autoFunction.hashCode());
result = prime * result + ((refRepeatable == null) ? 0 : refRepeatable.hashCode());
result = prime * result + ((repeatable == null) ? 0 : repeatable.hashCode());
result = prime * result + (useAbbreviatedComments ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
EolExtraCommentsOption other = (EolExtraCommentsOption) obj;
if (autoData != other.autoData) {
return false;
}
if (autoFunction != other.autoFunction) {
return false;
}
if (refRepeatable != other.refRepeatable) {
return false;
}
if (repeatable != other.repeatable) {
return false;
}
if (useAbbreviatedComments != other.useAbbreviatedComments) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,222 @@
/* ###
* 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.awt.Component;
import java.awt.event.ItemListener;
import java.beans.PropertyEditorSupport;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import ghidra.framework.options.CustomOptionsEditor;
import ghidra.util.layout.PairLayout;
public class EolExtraCommentsPropertyEditor extends PropertyEditorSupport
implements CustomOptionsEditor {
private static final String REPEATABLE_LABEL = "Repeatable Comment";
private static final String REF_REPEATABLE_LABEL = "Referenced Repeatable Comments";
private static final String AUTO_DATA_LABEL = "Auto Data Comment";
private static final String AUTO_FUNCTION_LABEL = "Auto Function Comment";
private static final String ABBREVIATED_LABEL = "Use Abbreviated Comments";
private static final String REPEATABLE_TOOLTIP =
"<HTML>For repeatable comments:" +
"<UL>" +
" <LI>ALWAYS - show even if an EOL comment exists</LI>" +
" <LI>DEFAULT - show only when no EOL comment exists</LI>" +
" <LI>NEVER - do not show</LI>" +
"</UL>";
private static final String REF_REPEATABLE_TOOLTIP =
"<HTML>For referenced repeatable comments:" +
"<UL>" +
" <LI>ALWAYS - show even if a higher priority comment exists</LI>" +
" <LI>DEFAULT - show only when no higher priority comment exists</LI>" +
" <LI>NEVER - do not show</LI>" +
"</UL>";
private static final String AUTO_TOOLTIP =
"<HTML>For automatic comments:" +
"<UL>" +
" <LI>ALWAYS - show even if a higher priority comment exists</LI>" +
" <LI>DEFAULT - show only when no higher priority comment exists</LI>" +
" <LI>NEVER - do not show</LI>" +
"</UL>";
private static final String ABBREVIATED_TOOLTIP =
"When showing automatic comments, show the smallest amount of information possible";
private static final String[] NAMES =
{ REPEATABLE_LABEL, REF_REPEATABLE_LABEL, AUTO_DATA_LABEL, AUTO_FUNCTION_LABEL,
ABBREVIATED_LABEL };
private static final String[] DESCRIPTIONS = {
REPEATABLE_TOOLTIP, REF_REPEATABLE_TOOLTIP, AUTO_TOOLTIP, AUTO_TOOLTIP,
ABBREVIATED_TOOLTIP
};
private Component editorComponent;
private GComboBox<EolEnablement> repeatableCombo;
private GComboBox<EolEnablement> refRepeatableCombo;
private GComboBox<EolEnablement> autoDataCombo;
private GComboBox<EolEnablement> autoFunctionCombo;
private JCheckBox abbreviatedCheckbox;
private EolExtraCommentsOption commentsOption;
public EolExtraCommentsPropertyEditor() {
editorComponent = buildEditor();
}
private Component buildEditor() {
// values picked through trial-and-error
int vgap = 3;
int hgap = 5;
int minRightSize = 150; // big enough to match other items in the external options panel
JPanel panel = new JPanel(new PairLayout(vgap, hgap, minRightSize));
JLabel label = new JLabel(REPEATABLE_LABEL);
label.setToolTipText(REPEATABLE_TOOLTIP);
repeatableCombo = new GComboBox<>(EolEnablement.values());
repeatableCombo.setSelectedItem(EolEnablement.DEFAULT);
repeatableCombo.addItemListener(e -> firePropertyChange());
panel.add(label);
panel.add(repeatableCombo);
label = new JLabel(REF_REPEATABLE_LABEL);
label.setToolTipText(REF_REPEATABLE_TOOLTIP);
refRepeatableCombo = new GComboBox<>(EolEnablement.values());
refRepeatableCombo.setSelectedItem(EolEnablement.DEFAULT);
refRepeatableCombo.addItemListener(e -> firePropertyChange());
panel.add(label);
panel.add(refRepeatableCombo);
label = new JLabel(AUTO_DATA_LABEL);
label.setToolTipText(AUTO_TOOLTIP);
autoDataCombo = new GComboBox<>(EolEnablement.values());
autoDataCombo.setSelectedItem(EolEnablement.DEFAULT);
autoDataCombo.addItemListener(e -> firePropertyChange());
panel.add(label);
panel.add(autoDataCombo);
label = new JLabel(AUTO_FUNCTION_LABEL);
label.setToolTipText(AUTO_TOOLTIP);
autoFunctionCombo = new GComboBox<>(EolEnablement.values());
autoFunctionCombo.setSelectedItem(EolEnablement.DEFAULT);
autoFunctionCombo.addItemListener(e -> firePropertyChange());
panel.add(label);
panel.add(autoFunctionCombo);
abbreviatedCheckbox = new GCheckBox(ABBREVIATED_LABEL);
abbreviatedCheckbox.setSelected(true);
abbreviatedCheckbox.setToolTipText(ABBREVIATED_TOOLTIP);
ItemListener listener = e -> firePropertyChange();
repeatableCombo.addItemListener(listener);
refRepeatableCombo.addItemListener(listener);
autoDataCombo.addItemListener(listener);
autoFunctionCombo.addItemListener(listener);
abbreviatedCheckbox.addItemListener(listener);
panel.setBorder(BorderFactory.createCompoundBorder(
new TitledBorder("Additional Comment Types"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
return panel;
}
private Object cloneOptionValues() {
EolExtraCommentsOption newOption = new EolExtraCommentsOption();
newOption.setRepeatable((EolEnablement) repeatableCombo.getSelectedItem());
newOption.setRefRepeatable((EolEnablement) refRepeatableCombo.getSelectedItem());
newOption.setAutoData((EolEnablement) autoDataCombo.getSelectedItem());
newOption.setAutoFunction((EolEnablement) autoFunctionCombo.getSelectedItem());
return newOption;
}
@Override
public String[] getOptionNames() {
return NAMES;
}
@Override
public String[] getOptionDescriptions() {
return DESCRIPTIONS;
}
@Override
public Object getValue() {
return cloneOptionValues();
}
@Override
public Component getCustomEditor() {
return editorComponent;
}
@Override
public boolean supportsCustomEditor() {
return true;
}
@Override
public void setValue(Object value) {
if (!(value instanceof EolExtraCommentsOption)) {
return;
}
commentsOption = (EolExtraCommentsOption) value;
setLocalValues(commentsOption);
firePropertyChange();
}
private void setLocalValues(EolExtraCommentsOption sourceOption) {
EolEnablement currentPriority = (EolEnablement) repeatableCombo.getSelectedItem();
EolEnablement newPriority = sourceOption.getRepeatable();
if (currentPriority != newPriority) {
repeatableCombo.setSelectedItem(newPriority);
}
currentPriority = (EolEnablement) refRepeatableCombo.getSelectedItem();
newPriority = sourceOption.getRefRepeatable();
if (currentPriority != newPriority) {
refRepeatableCombo.setSelectedItem(newPriority);
}
currentPriority = (EolEnablement) autoDataCombo.getSelectedItem();
newPriority = sourceOption.getAutoData();
if (currentPriority != newPriority) {
autoDataCombo.setSelectedItem(newPriority);
}
currentPriority = (EolEnablement) autoFunctionCombo.getSelectedItem();
newPriority = sourceOption.getAutoFunction();
if (currentPriority != newPriority) {
autoFunctionCombo.setSelectedItem(newPriority);
}
}
}

View File

@ -167,15 +167,14 @@ public class XRefFieldFactory extends FieldFactory {
private void setupNamespaceOptions(Options fieldOptions) {
// we need to install a custom editor that allows us to edit a group of related options
fieldOptions.registerOption(NAMESPACE_OPTIONS_KEY, OptionType.CUSTOM_TYPE,
new NamespaceWrappedOption(), null, "Adjusts the XREFs Field namespace display",
namespaceOptionsEditor);
new NamespaceWrappedOption(), new HelpLocation("CodeBrowserPlugin", "XREFs_Field"),
"Adjusts the XREFs Field namespace display", namespaceOptionsEditor);
CustomOption customOption = fieldOptions.getCustomOption(NAMESPACE_OPTIONS_KEY, null);
fieldOptions.getOptions(NAMESPACE_OPTIONS_KEY)
.setOptionsHelpLocation(new HelpLocation("CodeBrowserPlugin", "XREFs_Field"));
if (!(customOption instanceof NamespaceWrappedOption)) {
throw new AssertException("Someone set an option for " + NAMESPACE_OPTIONS_KEY +
" that is not the expected " +
"ghidra.app.util.viewer.field.NamespaceWrappedOption type.");
NamespaceWrappedOption.class.getName() + " type.");
}
NamespaceWrappedOption namespaceOption = (NamespaceWrappedOption) customOption;

View File

@ -578,29 +578,16 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testEOLCommentsOptions() throws Exception {
final int SHOW_AUTO = 0;
final int SHOW_REF_REPEAT = 1;
final int SHOW_REPEATABLE = 2;
final int WORD_WRAP = 3;
final int MAX_LINES = 4;
final int SHOW_REF_ADDR = 5;
//final int SHOW_FUNCTION_AUTO = 6;
final int SHOW_SEMICOLON = 7;
// for readability
String EXTRA_COMMENTS = EolCommentFieldFactory.EXTRA_COMMENT_KEY;
String WORD_WRAP = EolCommentFieldFactory.ENABLE_WORD_WRAP_KEY;
String MAX_LINES = EolCommentFieldFactory.MAX_DISPLAY_LINES_KEY;
String SHOW_REF_ADDR = EolCommentFieldFactory.ENABLE_PREPEND_REF_ADDRESS_KEY;
String SHOW_SEMICOLON = EolCommentFieldFactory.ENABLE_SHOW_SEMICOLON_KEY;
showTool(tool);
loadProgram();
Options options = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
List<String> names = getOptionNames(options, "EOL Comments Field");
assertEquals(9, names.size());
assertEquals(EolCommentFieldFactory.ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG, names.get(0));
assertEquals(EolCommentFieldFactory.ENABLE_ALWAYS_SHOW_REF_REPEATABLE_MSG, names.get(1));
assertEquals(EolCommentFieldFactory.ENABLE_ALWAYS_SHOW_REPEATABLE_MSG, names.get(2));
assertEquals(EolCommentFieldFactory.ENABLE_WORD_WRAP_MSG, names.get(3));
assertEquals(EolCommentFieldFactory.MAX_DISPLAY_LINES_MSG, names.get(4));
assertEquals(EolCommentFieldFactory.ENABLE_PREPEND_REF_ADDRESS_MSG, names.get(5));
assertEquals(EolCommentFieldFactory.SHOW_FUNCTION_AUTOMITIC_COMMENT_MSG, names.get(6));
assertEquals(EolCommentFieldFactory.ENABLE_SHOW_SEMICOLON_MSG, names.get(7));
assertEquals(EolCommentFieldFactory.USE_ABBREVIATED_AUTOMITIC_COMMENT_MSG, names.get(8));
Address callAddress = addr("0x1003fcc");
Address callRefAddress = addr("0x1006642");
Address otherRefAddress = addr("0x1003fa1");
@ -637,66 +624,77 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
CodeUnit.REPEATABLE_COMMENT, "Mem ref line1.\n" + "");
tool.execute(commentRefCmd, program);
options.setBoolean(names.get(SHOW_AUTO), false);
options.setBoolean(names.get(SHOW_REF_REPEAT), false);
options.setBoolean(names.get(SHOW_REPEATABLE), false);
options.setBoolean(names.get(WORD_WRAP), false);
options.setInt(names.get(MAX_LINES), 20);
options.setBoolean(names.get(SHOW_REF_ADDR), false);
options.setBoolean(names.get(SHOW_SEMICOLON), false);
// these values are all DEFAULT, by default; set them in case that changes in the future
EolExtraCommentsOption extraCommentsOption = new EolExtraCommentsOption();
extraCommentsOption.setRepeatable(EolEnablement.DEFAULT);
extraCommentsOption.setRefRepeatable(EolEnablement.DEFAULT);
extraCommentsOption.setAutoData(EolEnablement.DEFAULT);
extraCommentsOption.setAutoFunction(EolEnablement.DEFAULT);
options.setCustomOption(EXTRA_COMMENTS, extraCommentsOption);
options.setBoolean(WORD_WRAP, false);
options.setInt(MAX_LINES, 20);
options.setBoolean(SHOW_REF_ADDR, false);
options.setBoolean(SHOW_SEMICOLON, false);
cb.updateNow();
cb.goToField(callAddress, "EOL Comment", 0, 0);
ListingTextField btf = (ListingTextField) cb.getCurrentField();
assertEquals(9, getNumberOfLines(btf));
options.setBoolean(names.get(WORD_WRAP), true);
options.setBoolean(WORD_WRAP, true);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(18, getNumberOfLines(btf));
options.setBoolean(names.get(SHOW_SEMICOLON), true);
options.setBoolean(SHOW_SEMICOLON, true);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(20, getNumberOfLines(btf));
assertEquals("; ", btf.getFieldElement(1, 0).getText());
options.setBoolean(names.get(SHOW_REPEATABLE), true);
extraCommentsOption.setRepeatable(EolEnablement.ALWAYS);
options.setCustomOption(EXTRA_COMMENTS, extraCommentsOption);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(20, getNumberOfLines(btf));
assertEquals("; ", btf.getFieldElement(1, 0).getText());
options.setBoolean(names.get(SHOW_AUTO), true);
extraCommentsOption.setAutoData(EolEnablement.ALWAYS);
extraCommentsOption.setAutoFunction(EolEnablement.ALWAYS);
options.setCustomOption(EXTRA_COMMENTS, extraCommentsOption);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(20, getNumberOfLines(btf));
assertEquals("; ", btf.getFieldElement(1, 0).getText());
options.setBoolean(names.get(SHOW_REF_REPEAT), true);
extraCommentsOption.setRefRepeatable(EolEnablement.ALWAYS);
options.setCustomOption(EXTRA_COMMENTS, extraCommentsOption);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(20, getNumberOfLines(btf));
assertEquals("; ", btf.getFieldElement(1, 0).getText());
options.setBoolean(names.get(SHOW_REF_ADDR), true);
options.setBoolean(EolCommentFieldFactory.ENABLE_PREPEND_REF_ADDRESS_KEY, true);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(20, getNumberOfLines(btf));
assertEquals("; ", btf.getFieldElement(1, 0).getText());
options.setBoolean(names.get(SHOW_REPEATABLE), false);
extraCommentsOption.setRepeatable(EolEnablement.DEFAULT);
options.setCustomOption(EXTRA_COMMENTS, extraCommentsOption);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(20, getNumberOfLines(btf));
assertEquals("; ", btf.getFieldElement(1, 0).getText());
options.setBoolean(names.get(WORD_WRAP), false);
options.setBoolean(WORD_WRAP, false);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(12, getNumberOfLines(btf));
assertTrue("; ".equals(btf.getFieldElement(5, 0).getText()));
options.setBoolean(names.get(SHOW_SEMICOLON), false);
options.setBoolean(SHOW_SEMICOLON, false);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(12, getNumberOfLines(btf));
@ -704,7 +702,7 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
assertEquals("01003fa1", btf.getFieldElement(11, 4).getText());
assertEquals("Mem ref line1.", btf.getFieldElement(11, 11).getText());
options.setBoolean(names.get(SHOW_REF_ADDR), false);
options.setBoolean(SHOW_REF_ADDR, false);
cb.updateNow();
btf = (ListingTextField) cb.getCurrentField();
assertEquals(11, getNumberOfLines(btf));

View File

@ -247,8 +247,8 @@ public class CommentsPluginTest extends AbstractGhidraHeadedIntegrationTest {
setFieldWidth(browser, EolCommentFieldFactory.FIELD_NAME, 100);
Options options = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
options.setBoolean(EolCommentFieldFactory.ENABLE_WORD_WRAP_MSG, true);
options.setInt(EolCommentFieldFactory.MAX_DISPLAY_LINES_MSG, 100);
options.setBoolean(EolCommentFieldFactory.ENABLE_WORD_WRAP_KEY, true);
options.setInt(EolCommentFieldFactory.MAX_DISPLAY_LINES_KEY, 100);
runSwing(() -> tool.getToolFrame().setSize(800, 800));

View File

@ -65,7 +65,7 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
ListingTextField tf = getFieldText(function);
assertEquals(2, tf.getNumRows());
setBooleanOption(EolCommentFieldFactory.ENABLE_WORD_WRAP_MSG, true);
setBooleanOption(EolCommentFieldFactory.ENABLE_WORD_WRAP_KEY, true);
tf = getFieldText(function);
assertEquals(4, tf.getNumRows());

View File

@ -243,7 +243,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
// skip options that are "not simple", i.e. have custom editors
if (simpleName.equals("Display Namespace") ||
simpleName.equals("Array Display Options") ||
simpleName.equals("Address Display Options")) {
simpleName.equals("Address Display Options") ||
simpleName.equals("Auto Comments")) {
continue;
}

View File

@ -18,13 +18,16 @@ package ghidra.app.plugin.core.comments;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.app.cmd.refs.AddMemRefCmd;
import ghidra.app.util.DisplayableEol;
import ghidra.app.util.EolComments;
import ghidra.app.util.viewer.field.EolEnablement;
import ghidra.app.util.viewer.field.EolExtraCommentsOption;
import ghidra.framework.cmd.Command;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
@ -37,11 +40,11 @@ import ghidra.program.model.symbol.SourceType;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.util.exception.RollbackException;
public class DisplayableEolTest extends AbstractGenericTest {
public class EolCommentsTest extends AbstractGenericTest {
private ProgramDB program;
public DisplayableEolTest() {
public EolCommentsTest() {
super();
}
@ -72,12 +75,22 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr("0x110"));
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, true, true);
String[] comments = displayableEol.getAutomaticComment();
assertEquals(1, comments.length);
assertEquals("? -> 00000120", comments[0]);
EolExtraCommentsOption eolOption = createShowAllOption();
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
List<String> comments = eolComments.getAutomaticComment();
assertEquals(1, comments.size());
assertEquals("? -> 00000120", comments.get(0));
}
private EolExtraCommentsOption createShowAllOption() {
EolExtraCommentsOption eolOption = new EolExtraCommentsOption();
eolOption.setRepeatable(EolEnablement.ALWAYS);
eolOption.setRefRepeatable(EolEnablement.ALWAYS);
eolOption.setAutoData(EolEnablement.ALWAYS);
eolOption.setAutoFunction(EolEnablement.ALWAYS);
return eolOption;
}
@Test
@ -89,12 +102,12 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr("0x1001000"));
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, true, true);
EolExtraCommentsOption eolOption = createShowAllOption();
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
String[] comments = displayableEol.getAutomaticComment();
assertEquals(1, comments.length);
assertEquals("= \"one.two\"", comments[0]);
List<String> comments = eolComments.getAutomaticComment();
assertEquals(1, comments.size());
assertEquals("= \"one.two\"", comments.get(0));
}
@Test
@ -109,15 +122,15 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr("0x1001000"));
EolExtraCommentsOption eolOption = createShowAllOption();
// with this at false, all of the string will be rendered
boolean useAbbreviatedComments = false;
eolOption.setUseAbbreviatedComments(false);
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, useAbbreviatedComments, true);
String[] comments = displayableEol.getAutomaticComment();
assertEquals(1, comments.length);
assertEquals("= \"one.two\"", comments[0]);
List<String> comments = eolComments.getAutomaticComment();
assertEquals(1, comments.size());
assertEquals("= \"one.two\"", comments.get(0));
}
@Test
@ -132,16 +145,16 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr("0x1001000"));
// with this at false, all of the string will be rendered
boolean useAbbreviatedComments = false;
boolean showAutoFunctions = false;
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, useAbbreviatedComments,
showAutoFunctions);
EolExtraCommentsOption eolOption = createShowAllOption();
String[] comments = displayableEol.getAutomaticComment();
assertEquals(1, comments.length);
assertEquals("= \"one.two\"", comments[0]);
// with this at false, all of the string will be rendered
eolOption.setUseAbbreviatedComments(false);
eolOption.setAutoFunction(EolEnablement.NEVER);
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
List<String> comments = eolComments.getAutomaticComment();
assertEquals(1, comments.size());
assertEquals("= \"one.two\"", comments.get(0));
}
@Test
@ -159,15 +172,15 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr("0x1001000"));
EolExtraCommentsOption eolOption = createShowAllOption();
// with this at true, only the used part of the string will be rendered
boolean useAbbreviatedComments = true;
eolOption.setUseAbbreviatedComments(true);
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, useAbbreviatedComments, true);
String[] comments = displayableEol.getAutomaticComment();
assertEquals(1, comments.length);
assertEquals("= \"two\"", comments[0]);// full string is one.two
List<String> comments = eolComments.getAutomaticComment();
assertEquals(1, comments.size());
assertEquals("= \"two\"", comments.get(0));// full string is one.two
}
@Test
@ -181,13 +194,13 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(from);
boolean showAutoFunctions = true;
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, false, showAutoFunctions);
String[] comments = displayableEol.getAutomaticComment();
assertEquals(1, comments.length);
assertEquals("undefined FUN_01001050()", comments[0]);
EolExtraCommentsOption eolOption = createShowAllOption();
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
List<String> comments = eolComments.getAutomaticComment();
assertEquals(1, comments.size());
assertEquals("undefined FUN_01001050()", comments.get(0));
}
@Test
@ -201,12 +214,16 @@ public class DisplayableEolTest extends AbstractGenericTest {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(from);
boolean showAutoFunctions = false;
DisplayableEol displayableEol =
new DisplayableEol(cu, true, true, true, false, 5, false, showAutoFunctions);
String[] comments = displayableEol.getAutomaticComment();
assertEquals(0, comments.length);
EolExtraCommentsOption eolOption = createShowAllOption();
// with this at false, all of the string will be rendered
eolOption.setUseAbbreviatedComments(false);
eolOption.setAutoFunction(EolEnablement.NEVER);
EolComments eolComments = new EolComments(cu, false, 5, eolOption);
List<String> comments = eolComments.getAutomaticComment();
assertEquals(0, comments.size());
}
public boolean applyCmd(Command cmd) throws RollbackException {

View File

@ -27,7 +27,6 @@ import ghidra.util.layout.HorizontalLayout;
public class CustomOptionComponent extends GenericOptionsComponent {
protected CustomOptionComponent(EditorState editorState) {
super(editorState);
// this layout allows us to easily left-align the single component in this container
setLayout(new HorizontalLayout(0));

View File

@ -32,7 +32,6 @@ public class DefaultOptionComponent extends GenericOptionsComponent {
private Component component;
public DefaultOptionComponent(EditorState editorState) {
super(editorState);
setLayout(new PairLayout(0, 6, 40));
this.component = editorState.getEditorComponent();
@ -71,7 +70,7 @@ public class DefaultOptionComponent extends GenericOptionsComponent {
}
@Override
protected void setAlignmentPreferredSize(Dimension dimension) {
protected void setPreferredAlignmentSize(Dimension dimension) {
label.setPreferredSize(dimension);
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,56 +15,56 @@
*/
package docking.options.editor;
import ghidra.framework.options.EditorState;
import java.awt.Dimension;
import java.util.List;
import javax.swing.JPanel;
import ghidra.framework.options.EditorState;
public abstract class GenericOptionsComponent extends JPanel {
protected final EditorState editorState;
/**
* Do not use this constructor directly. Instead, use the factory method:
* {@link #createOptionComponent(EditorState)}
*/
protected GenericOptionsComponent(EditorState editorState) {
this.editorState = editorState;
protected GenericOptionsComponent() {
// stub
}
/**
* A factory method to create new OptionComponents.
* @param state The state that will be used to create the correct OptionComponent
* @return the new OptionComponent.
*/
public static GenericOptionsComponent createOptionComponent( EditorState state ) {
if ( state.supportsCustomOptionsEditor() ) {
return new CustomOptionComponent( state );
}
return new DefaultOptionComponent( state );
}
/**
* A factory method to create new OptionComponents.
* @param state The state that will be used to create the correct OptionComponent
* @return the new OptionComponent.
*/
public static GenericOptionsComponent createOptionComponent(EditorState state) {
if (state.supportsCustomOptionsEditor()) {
return new CustomOptionComponent(state);
}
return new DefaultOptionComponent(state);
}
/**
* Creates and sets a preferred alignment based upon the given list of option components.
* @param components the list of options components from which to determine the alignment.
*/
/**
* Creates and sets a preferred alignment based upon the given list of option components.
* @param components the list of options components from which to determine the alignment.
*/
public static void alignLabels(List<GenericOptionsComponent> components) {
int maxWidth = 0;
int maxHeight = 0;
for (GenericOptionsComponent optionComponent : components) {
Dimension dimension = optionComponent.getPreferredAlignmentSize();
maxWidth = Math.max( dimension.width, maxWidth );
maxHeight = Math.max( dimension.height, maxHeight );
}
Dimension dimension = optionComponent.getPreferredAlignmentSize();
maxWidth = Math.max(dimension.width, maxWidth);
maxHeight = Math.max(dimension.height, maxHeight);
}
for (GenericOptionsComponent component : components) {
component.setAlignmentPreferredSize( new Dimension(maxWidth, maxHeight));
}
component.setPreferredAlignmentSize(new Dimension(maxWidth, maxHeight));
}
}
@Override
public void setEnabled(boolean enabled) {
public void setEnabled(boolean enabled) {
// stub
}
/**
@ -73,7 +72,8 @@ public abstract class GenericOptionsComponent extends JPanel {
* components.
* @param dimension The alignment dimension.
*/
protected void setAlignmentPreferredSize( Dimension dimension ) {
protected void setPreferredAlignmentSize(Dimension dimension) {
// stub
}
/**
@ -81,6 +81,6 @@ public abstract class GenericOptionsComponent extends JPanel {
* @return the alignment dimension.
*/
protected Dimension getPreferredAlignmentSize() {
return getPreferredSize();
return getPreferredSize();
}
}

View File

@ -18,33 +18,28 @@ package ghidra.framework.options;
public interface CustomOption {
/**
* <code>SaveState</code> key which corresponds to custom option
* implementation class. The use of this key/value within the stored
* state information is reserved for use by the option storage
* implementation and should be ignored by {@link #readState(SaveState)}
* implementation
* Key which corresponds to custom option implementation class. The use of this key/value
* within the stored state information is reserved for use by the option storage implementation
* and should be ignored by {@link #readState(GProperties)} implementation.
*/
public final String CUSTOM_OPTION_CLASS_NAME_KEY = "CUSTOM_OPTION_CLASS";
/**
* Concrete subclass of WrappedOption should read all of its
* state from the given saveState object.
* Read state from the given properties
* @param properties container of state information
*/
public void readState(GProperties properties);
/**
* Concrete subclass of WrappedOption should write all of its
* state to the given saveState object.
* Write state into the given properties
* @param properties container of state information
*/
public void writeState(GProperties properties);
/**
* CustomOption should implement this method to provide a formatted
* string value of this option value. The returned value will
* be used in support of the {@link Options#getValueAsString(String)}
* and {@link Options#getDefaultValueAsString(String)}.
* Subclasses should implement this method to provide a formatted string value of this option
* value. The returned value will be used in support of the
* {@link Options#getValueAsString(String)} and {@link Options#getDefaultValueAsString(String)}.
* @return option value as string
*/
@Override