diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm index d534c02f17..59f73b54a7 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm @@ -518,24 +518,46 @@

EOL Comments Field

-

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.

+

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. +

-

Always Show the Automatic Comment - 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.

+

Additional Comment Types - The following comment types may be optionally displayed + in the EOL Comments field. For each type below, the following setting may be applied: +

+

+ +

+ 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. +

+ +
+

Repeatable - The Repeatable Comment defined at the current + code unit address.

+ +

Referenced Repeatable - The Repeatable Comment that is defined at the + target reference address that the code unit at this address refers to.

+ +

Automatic Function - A preview of the referenced function.

+ +

Automatic Data - A preview of the referenced data. For example, a String + reference will show a preview of the target String.

+ +
-

Always Show the Referenced Repeatable Comment - 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.

- -

Always Show the Repeatable Comment - 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.

Enable Word Wrapping - 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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java deleted file mode 100644 index bcab9a615b..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java +++ /dev/null @@ -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 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 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 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 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 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 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 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 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 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 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 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)); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java new file mode 100644 index 0000000000..87f66792d2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java @@ -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. + * + *

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 eols = new ArrayList<>(); + private List repeatables = new ArrayList<>(); + private List refRepeatables = new ArrayList<>(); + private List autos = new ArrayList<>(); + private List 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 comments = + Arrays.asList(codeUnit.getCommentAsArray(CodeUnit.EOL_COMMENT)); + addStrings(comments, eols); + } + + private void loadRepeatables() { + boolean hasOtherComments = !eols.isEmpty(); + if (!extraCommentsOption.isShowingRepeatables(hasOtherComments)) { + return; + } + + Collection 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 refRepeatableComments = getRepeatableComments(true); + addRefRepeatables(refRepeatableComments, refRepeatables); + } + + private void loadAutos() { + boolean hasOtherComments = + !(eols.isEmpty() && repeatables.isEmpty() && refRepeatables.isEmpty()); + if (!extraCommentsOption.isShowingAutoComments(hasOtherComments)) { + return; + } + + Collection comments = getReferencePreviews(); + addStrings(comments, autos); + } + + private void addRefRepeatables(Collection from, + Collection 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 from, Collection 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 getReferencePreviews() { + + loadReferences(); + if (references.isEmpty()) { + return getPreviewForNoReferences(); + } + + int space = getAvailableSpace(); + Program program = codeUnit.getProgram(); + Set 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 getPreviewForNoReferences() { + Set 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 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 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 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 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 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 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 getRepeatableComments(boolean showAll) { + + loadReferences(); + int space = getAvailableSpace(); + Set 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 getComments() { + List 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 comments = getComments(); + return comments.toArray(new String[comments.size()]); + } + + /** + * Gets the End of Line comments + * @return the comments + */ + public List getEOLComments() { + return Collections.unmodifiableList(eols); + } + + /** + * Gets the repeatable comments + * @return the comments + */ + public List getRepeatableComments() { + return Collections.unmodifiableList(repeatables); + } + + /** + * Gets the repeatable comments at the "to reference"s + * @return the comments + */ + public List getReferencedRepeatableComments() { + return Collections.unmodifiableList(refRepeatables); + } + + /** + * Gets the automatic comments + * @return the comments + */ + public List 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; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java index e8ea62ffca..1ab99d4a44 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java @@ -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 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) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java index 344366eaf0..f295289175 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java @@ -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 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 eolFieldElements = convertToFieldElements(program, eolComments, - myEolPrefixString, showSemicolon, isWordWrap, getNextRow(elementList)); - elementList.addAll(eolFieldElements); + List elementList = new ArrayList<>(); + AttributedString prefix = createPrefix(style); + List eols = comments.getEOLComments(); + List 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 repeatableFieldElements = - convertToFieldElements(program, repeatableComments, myRepeatablePrefixString, - showSemicolon, isWordWrap, getNextRow(elementList)); - elementList.addAll(repeatableFieldElements); + if (comments.isShowingRepeatables()) { + prefix = createPrefix(repeatableCommentStyle); + int row = getNextRow(elementList); + List repeatables = comments.getRepeatableComments(); + List 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 refRepeatFieldElements = convertToRefFieldElements( - refRepeatComments, program, refRepeatPrefixString, showSemicolon, isWordWrap, - prependRefAddress, refRepeatComment.getAddress(), getNextRow(elementList)); - elementList.addAll(refRepeatFieldElements); + if (comments.isShowingRefRepeatables()) { + prefix = createPrefix(refRepeatableCommentStyle); + List refRepeatables = + comments.getReferencedRepeatableComments(); + for (RefRepeatComment comment : refRepeatables) { + int row = getNextRow(elementList); + String[] lines = comment.getCommentLines(); + List 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 autoCommentFieldElements = - convertToFieldElements(program, autoComment, autoCommentPrefixString, showSemicolon, - isWordWrap, getNextRow(elementList)); - elementList.addAll(autoCommentFieldElements); + if (comments.isShowingAutoComments()) { + prefix = createPrefix(automaticCommentStyle); + int row = getNextRow(elementList); + List autos = comments.getAutomaticComment(); + List 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 elementList) { int elementIndex = elementList.size() - 1; if (elementIndex >= 0) { @@ -352,26 +338,25 @@ public class EolCommentFieldFactory extends FieldFactory { return 0; } - private List convertToFieldElements(Program program, String[] comments, - AttributedString currentPrefixString, boolean showPrefix, boolean wordWrap, - int nextRow) { + private List convertToFieldElements(Program program, List comments, + AttributedString currentPrefixString, int nextRow) { List 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 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 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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolEnablement.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolEnablement.java new file mode 100644 index 0000000000..8390e69065 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolEnablement.java @@ -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; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolExtraCommentsOption.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolExtraCommentsOption.java new file mode 100644 index 0000000000..a1422dbc59 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolExtraCommentsOption.java @@ -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; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolExtraCommentsPropertyEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolExtraCommentsPropertyEditor.java new file mode 100644 index 0000000000..8bd4d3910b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolExtraCommentsPropertyEditor.java @@ -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 = + "For repeatable comments:" + + "

"; + + private static final String REF_REPEATABLE_TOOLTIP = + "For referenced repeatable comments:" + + ""; + + private static final String AUTO_TOOLTIP = + "For automatic comments:" + + ""; + + 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 repeatableCombo; + private GComboBox refRepeatableCombo; + private GComboBox autoDataCombo; + private GComboBox 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); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java index 0da5697ab9..a7b127977c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java @@ -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; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java index 77f1873f0d..65e44e8009 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java @@ -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 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)); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java index fa17f4c27f..5bbdaf8dfb 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java @@ -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)); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java index 28233f4005..9a495c0c8a 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java @@ -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()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java index c113dfb670..69aa95fe05 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java @@ -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; } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/comments/DisplayableEolTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/comments/EolCommentsTest.java similarity index 64% rename from Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/comments/DisplayableEolTest.java rename to Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/comments/EolCommentsTest.java index f3c26b48dc..7901707024 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/comments/DisplayableEolTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/comments/EolCommentsTest.java @@ -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 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 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 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 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 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 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 comments = eolComments.getAutomaticComment(); + assertEquals(0, comments.size()); } public boolean applyCmd(Command cmd) throws RollbackException { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/CustomOptionComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/CustomOptionComponent.java index 75ac057733..4f647f524e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/CustomOptionComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/CustomOptionComponent.java @@ -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)); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java index d6bdcfed9d..69ca30da2c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java @@ -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); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/GenericOptionsComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/GenericOptionsComponent.java index f0872d335e..ebab26deb5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/GenericOptionsComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/GenericOptionsComponent.java @@ -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 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(); } } diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/CustomOption.java b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/CustomOption.java index cc27a7f9f8..555cb241ef 100644 --- a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/CustomOption.java +++ b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/CustomOption.java @@ -18,33 +18,28 @@ package ghidra.framework.options; public interface CustomOption { /** - * SaveState 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