Merge remote-tracking branch 'origin/Ghidra_11.3'

This commit is contained in:
Ryan Kurtz
2025-01-24 13:40:58 -05:00
19 changed files with 308 additions and 256 deletions
@@ -73,8 +73,8 @@ public class SetCommentCmd implements Command<Program> {
" Is this address valid?"; " Is this address valid?";
return false; return false;
} }
String updatedComment = CommentUtils.fixupAnnotations(comment, program);
updatedComment = CommentUtils.sanitize(updatedComment); String updatedComment = CommentUtils.sanitize(comment);
if (commentChanged(cu.getComment(commentType), updatedComment)) { if (commentChanged(cu.getComment(commentType), updatedComment)) {
cu.setComment(commentType, updatedComment); cu.setComment(commentType, updatedComment);
} }
@@ -32,7 +32,7 @@ import docking.widgets.OptionDialog;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import ghidra.app.util.viewer.field.AnnotatedStringHandler; import ghidra.app.util.viewer.field.AnnotatedStringHandler;
import ghidra.app.util.viewer.field.Annotation; import ghidra.app.util.viewer.field.CommentUtils;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@@ -225,7 +225,7 @@ public class CommentsDialog extends ReusableDialogComponentProvider implements K
} }
private AnnotationAdapterWrapper[] getAnnotationAdapterWrappers() { private AnnotationAdapterWrapper[] getAnnotationAdapterWrappers() {
List<AnnotatedStringHandler> annotations = Annotation.getAnnotatedStringHandlers(); List<AnnotatedStringHandler> annotations = CommentUtils.getAnnotatedStringHandlers();
int count = annotations.size(); int count = annotations.size();
AnnotationAdapterWrapper[] retVal = new AnnotationAdapterWrapper[count]; AnnotationAdapterWrapper[] retVal = new AnnotationAdapterWrapper[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
@@ -15,134 +15,36 @@
*/ */
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.field.AttributedString;
import ghidra.app.nav.Navigatable;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.classfinder.ClassSearcher;
public class Annotation { public class Annotation {
public static final String ESCAPABLE_CHARS = "{}\"\\"; public static final String ESCAPABLE_CHARS = "{}\"\\";
private static List<AnnotatedStringHandler> ANNOTATED_STRING_HANDLERS;
private static Map<String, AnnotatedStringHandler> ANNOTATED_STRING_MAP;
private String annotationText; private String annotationText;
private String[] annotationParts; private String[] annotationParts;
private AnnotatedStringHandler annotatedStringHandler;
private AttributedString displayString;
public static List<AnnotatedStringHandler> getAnnotatedStringHandlers() {
if (ANNOTATED_STRING_HANDLERS == null) {
ANNOTATED_STRING_HANDLERS = getSupportedAnnotationHandlers();
}
return ANNOTATED_STRING_HANDLERS;
}
private static Map<String, AnnotatedStringHandler> getAnnotatedStringHandlerMap() {
if (ANNOTATED_STRING_MAP == null) { // lazy init due to our use of ClassSearcher
ANNOTATED_STRING_MAP = createAnnotatedStringHandlerMap();
}
return ANNOTATED_STRING_MAP;
}
private static Map<String, AnnotatedStringHandler> createAnnotatedStringHandlerMap() {
Map<String, AnnotatedStringHandler> map = new HashMap<>();
for (AnnotatedStringHandler instance : getAnnotatedStringHandlers()) {
String[] supportedAnnotations = instance.getSupportedAnnotations();
for (String supportedAnnotation : supportedAnnotations) {
map.put(supportedAnnotation, instance);
}
}
return Collections.unmodifiableMap(map);
}
// locates AnnotatedStringHandler implementations to handle annotations
private static List<AnnotatedStringHandler> getSupportedAnnotationHandlers() {
List<AnnotatedStringHandler> list = new ArrayList<>();
for (AnnotatedStringHandler h : ClassSearcher.getInstances(AnnotatedStringHandler.class)) {
if (h.getSupportedAnnotations().length != 0) {
list.add(h);
}
}
return Collections.unmodifiableList(list);
}
/** /**
* Constructor * Constructor
* <b>Note</b>: This constructor assumes that the string starts with "{<pre>@</pre>" and ends with '}' * <b>Note</b>: This constructor assumes that the string starts with "{<pre>@</pre>" and ends with '}'
* *
* @param annotationText The complete annotation text. * @param annotationText The complete annotation text.
* @param prototypeString An AttributedString that provides the attributes for the display
* text this Annotation can create * text this Annotation can create
* @param program the program * @param program the program
*/ */
public Annotation(String annotationText, AttributedString prototypeString, Program program) { public Annotation(String annotationText, Program program) {
this.annotationText = annotationText; this.annotationText = annotationText;
annotationParts = parseAnnotationText(annotationText); this.annotationParts = parseAnnotationText(annotationText);
annotatedStringHandler = getHandler(annotationParts);
try {
displayString = annotatedStringHandler.createAnnotatedString(prototypeString,
annotationParts, program);
}
catch (AnnotationException ae) {
// uh-oh
annotatedStringHandler =
new InvalidAnnotatedStringHandler("Annotation Exception: " + ae.getMessage());
displayString = annotatedStringHandler.createAnnotatedString(prototypeString,
annotationParts, program);
}
} }
private AnnotatedStringHandler getHandler(String[] annotationPieces) { public String[] getAnnotationParts() {
if (annotationPieces.length <= 1) {
return new InvalidAnnotatedStringHandler(
"Invalid annotation format." + " Expected at least two strings.");
}
// the first part is the annotation (@xxx)
String keyword = annotationPieces[0];
AnnotatedStringHandler handler = getAnnotatedStringHandlerMap().get(keyword);
if (handler == null) {
return new InvalidAnnotatedStringHandler("Invalid annotation keyword: " + keyword);
}
return handler;
}
String[] getAnnotationParts() {
return annotationParts; return annotationParts;
} }
AnnotatedStringHandler getHandler() {
return annotatedStringHandler;
}
public AttributedString getDisplayString() {
return displayString;
}
/**
* Called when a mouse click occurs on a FieldElement containing this Annotation.
*
* @param sourceNavigatable The source navigatable associated with the mouse click.
* @param serviceProvider The service provider to be used when creating
* {@link AnnotatedStringHandler} instances.
* @return true if the handler desires to handle the mouse click.
*/
public boolean handleMouseClick(Navigatable sourceNavigatable,
ServiceProvider serviceProvider) {
return annotatedStringHandler.handleMouseClick(annotationParts, sourceNavigatable,
serviceProvider);
}
public String getAnnotationText() { public String getAnnotationText() {
return annotationText; return annotationText;
} }
@@ -152,10 +54,6 @@ public class Annotation {
return annotationText; return annotationText;
} }
/*package*/ static Set<String> getAnnotationNames() {
return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet());
}
private String[] parseAnnotationText(String text) { private String[] parseAnnotationText(String text) {
String trimmed = text.substring(2, text.length() - 1); // remove "{@" and '}' String trimmed = text.substring(2, text.length() - 1); // remove "{@" and '}'
@@ -15,9 +15,6 @@
*/ */
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import docking.widgets.fieldpanel.field.AbstractTextFieldElement;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
public class AnnotationCommentPart extends CommentPart { public class AnnotationCommentPart extends CommentPart {
private Annotation annotation; private Annotation annotation;
@@ -32,13 +29,12 @@ public class AnnotationCommentPart extends CommentPart {
return annotation.getAnnotationText(); return annotation.getAnnotationText();
} }
@Override
AbstractTextFieldElement createElement(int row, int column) {
return new AnnotatedTextFieldElement(annotation, row, column);
}
@Override @Override
public String toString() { public String toString() {
return annotation.toString(); return annotation.toString();
} }
public Annotation getAnnotation() {
return annotation;
}
} }
@@ -15,8 +15,6 @@
*/ */
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import docking.widgets.fieldpanel.field.AbstractTextFieldElement;
public abstract class CommentPart { public abstract class CommentPart {
protected String displayText; protected String displayText;
@@ -25,8 +23,6 @@ public abstract class CommentPart {
this.displayText = displayText; this.displayText = displayText;
} }
abstract AbstractTextFieldElement createElement(int row, int column);
abstract String getRawText(); abstract String getRawText();
String getDisplayText() { String getDisplayText() {
@@ -16,9 +16,8 @@
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.awt.*; import java.awt.*;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -28,15 +27,24 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui; import generic.theme.Gui;
import ghidra.app.util.NamespaceUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.StringUtilities; import ghidra.util.StringUtilities;
import ghidra.util.WordLocation; import ghidra.util.WordLocation;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.classfinder.ClassSearcher;
public class CommentUtils { public class CommentUtils {
// looks like: {@sym|symbol|... // looks like: {@sym|symbol|...
private static final Pattern ANNOTATION_START_PATTERN = createAnnotationStartPattern(); private static final Pattern ANNOTATION_START_PATTERN = createAnnotationStartPattern();
private static List<AnnotatedStringHandler> ANNOTATED_STRING_HANDLERS;
private static Map<String, AnnotatedStringHandler> ANNOTATED_STRING_MAP;
/** /**
* Makes adjustments as necessary to any annotations in the given text. * Makes adjustments as necessary to any annotations in the given text.
* *
@@ -50,31 +58,28 @@ public class CommentUtils {
return null; return null;
} }
AttributedString prototype = createPrototype();
// this function will take any given Symbol annotations and change the text, replacing // this function will take any given Symbol annotations and change the text, replacing
// the symbol name with the address of the symbol // the symbol name with the address of the symbol
Function<Annotation, Annotation> symbolFixer = annotation -> { Function<Annotation, Annotation> symbolFixer = annotation -> {
AnnotatedStringHandler handler = annotation.getHandler(); String[] annotationParts = annotation.getAnnotationParts();
AnnotatedStringHandler handler = getAnnotationHandler(annotationParts);
if (!(handler instanceof SymbolAnnotatedStringHandler)) { if (!(handler instanceof SymbolAnnotatedStringHandler)) {
return annotation; // nothing to change return annotation; // nothing to change
} }
String rawText = annotation.getAnnotationText(); String rawText = annotation.getAnnotationText();
String[] annotationParts = annotation.getAnnotationParts(); String updatedText =
String updatedText = SymbolAnnotatedStringHandler.convertAnnotationSymbolToAddress( convertAnnotationSymbolToAddress(annotationParts, rawText, program);
annotationParts, rawText, program);
if (updatedText == null) { if (updatedText == null) {
return annotation; // nothing to change return annotation; // nothing to change
} }
return new Annotation(updatedText, prototype, program); return new Annotation(updatedText, program);
}; };
StringBuilder buffy = new StringBuilder(); StringBuilder buffy = new StringBuilder();
List<CommentPart> parts = List<CommentPart> parts = doParseTextIntoParts(rawCommentText, symbolFixer, program);
doParseTextIntoTextAndAnnotations(rawCommentText, symbolFixer, program, prototype);
for (CommentPart part : parts) { for (CommentPart part : parts) {
buffy.append(part.getRawText()); buffy.append(part.getRawText());
} }
@@ -121,7 +126,7 @@ public class CommentUtils {
AttributedString prototypeString, int row) { AttributedString prototypeString, int row) {
Function<Annotation, Annotation> noFixing = Function.identity(); Function<Annotation, Annotation> noFixing = Function.identity();
return doParseTextForAnnotations(text, noFixing, program, prototypeString, row); return createFieldElementForAnnotations(text, noFixing, program, prototypeString, row);
} }
/** /**
@@ -154,7 +159,7 @@ public class CommentUtils {
* @param row the row of the newly created FieldElement * @param row the row of the newly created FieldElement
* @return A field element containing {@link AttributedString}s * @return A field element containing {@link AttributedString}s
*/ */
private static FieldElement doParseTextForAnnotations(String text, private static FieldElement createFieldElementForAnnotations(String text,
Function<Annotation, Annotation> fixerUpper, Program program, Function<Annotation, Annotation> fixerUpper, Program program,
AttributedString prototype, int row) { AttributedString prototype, int row) {
@@ -163,16 +168,30 @@ public class CommentUtils {
int column = 0; int column = 0;
List<CommentPart> parts = List<CommentPart> parts =
doParseTextIntoTextAndAnnotations(text, fixerUpper, program, prototype); doParseTextIntoParts(text, fixerUpper, program);
List<FieldElement> fields = new ArrayList<>(); List<FieldElement> fields = new ArrayList<>();
for (CommentPart part : parts) { for (CommentPart part : parts) {
fields.add(part.createElement(row, column));
FieldElement f = createElement(part, prototype, program, row, column);
fields.add(f);
column += part.getDisplayText().length(); column += part.getDisplayText().length();
} }
return new CompositeFieldElement(fields.toArray(new FieldElement[fields.size()])); return new CompositeFieldElement(fields.toArray(new FieldElement[fields.size()]));
} }
private static FieldElement createElement(CommentPart part, AttributedString prototype,
Program p, int row, int column) {
if (part instanceof AnnotationCommentPart annotationPart) {
Annotation annotation = annotationPart.getAnnotation();
return new AnnotatedTextFieldElement(annotation, prototype, p, row, column);
}
AttributedString as = prototype.deriveAttributedString(part.getDisplayText());
return new TextFieldElement(as, row, column);
}
/** /**
* Split the given text into parts where the returned list contains either a String or * Split the given text into parts where the returned list contains either a String or
* an Annotation * an Annotation
@@ -180,18 +199,16 @@ public class CommentUtils {
* @param text the text to parse * @param text the text to parse
* @param fixerUpper a function that is given a chance to convert an Annotation into a new one * @param fixerUpper a function that is given a chance to convert an Annotation into a new one
* @param program the program * @param program the program
* @param prototype the prototype string that contains decoration attributes
* @return a list that contains a mixture String or an Annotation entries * @return a list that contains a mixture String or an Annotation entries
*/ */
private static List<CommentPart> doParseTextIntoTextAndAnnotations(String text, private static List<CommentPart> doParseTextIntoParts(String text,
Function<Annotation, Annotation> fixerUpper, Program program, Function<Annotation, Annotation> fixerUpper, Program program) {
AttributedString prototype) {
List<CommentPart> results = new ArrayList<>(); List<CommentPart> results = new ArrayList<>();
List<WordLocation> annotations = getCommentAnnotations(text); List<WordLocation> annotations = getCommentAnnotations(text);
if (annotations.isEmpty()) { if (annotations.isEmpty()) {
results.add(new StringCommentPart(text, prototype)); results.add(new StringCommentPart(text));
return results; return results;
} }
@@ -203,11 +220,11 @@ public class CommentUtils {
if (offset != start) { if (offset != start) {
// text between annotations // text between annotations
String preceding = text.substring(offset, start); String preceding = text.substring(offset, start);
results.add(new StringCommentPart(preceding, prototype)); results.add(new StringCommentPart(preceding));
} }
String annotationText = word.getWord(); String annotationText = word.getWord();
Annotation annotation = new Annotation(annotationText, prototype, program); Annotation annotation = new Annotation(annotationText, program);
annotation = fixerUpper.apply(annotation); annotation = fixerUpper.apply(annotation);
results.add(new AnnotationCommentPart(annotationText, annotation)); results.add(new AnnotationCommentPart(annotationText, annotation));
@@ -216,7 +233,7 @@ public class CommentUtils {
if (offset != text.length()) { // trailing text if (offset != text.length()) { // trailing text
String trailing = text.substring(offset); String trailing = text.substring(offset);
results.add(new StringCommentPart(trailing, prototype)); results.add(new StringCommentPart(trailing));
} }
return results; return results;
@@ -249,7 +266,7 @@ public class CommentUtils {
private static Pattern createAnnotationStartPattern() { private static Pattern createAnnotationStartPattern() {
Set<String> names = Annotation.getAnnotationNames(); Set<String> names = getAnnotationNames();
String namePatternString = StringUtils.join(names, "|"); String namePatternString = StringUtils.join(names, "|");
//@formatter:off //@formatter:off
@@ -304,4 +321,123 @@ public class CommentUtils {
return -1; return -1;
} }
private static String convertAnnotationSymbolToAddress(String[] annotationParts, String rawText,
Program program) {
if (annotationParts.length <= 1) {
return null;
}
if (program == null) { // this can happen during merge operations
return null;
}
Address address = program.getAddressFactory().getAddress(annotationParts[1]);
if (address != null) {
return null; // nothing to do
}
String originalValue = annotationParts[1];
List<Symbol> symbols = getSymbols(originalValue, program);
if (symbols.size() != 1) {
// no unique symbol, so leave it as string name
return null;
}
Address symbolAddress = symbols.get(0).getAddress();
return rawText.replaceFirst(Pattern.quote(originalValue), symbolAddress.toString());
}
/**
* Returns all symbols that match the given text or an empty list.
* @param rawText the raw symbol text
* @param program the program
* @return the symbols
*/
public static List<Symbol> getSymbols(String rawText, Program program) {
List<Symbol> list = NamespaceUtils.getSymbols(rawText, program);
if (!list.isEmpty()) {
return list;
}
// if we get here, then see if the value is an address
Address address = program.getAddressFactory().getAddress(rawText);
if (address != null) {
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(address);
if (symbol != null) {
return Arrays.asList(symbol);
}
}
return Collections.emptyList();
}
/**
* Returns the annotation handler for the given annotation parts. If no handler can be found,
* then the {@link InvalidAnnotatedStringHandler} will be returned with n error message.
* @param annotationParts the annotation parts
* @return the handler
*/
public static AnnotatedStringHandler getAnnotationHandler(String[] annotationParts) {
if (annotationParts.length <= 1) {
return new InvalidAnnotatedStringHandler(
"Invalid annotation format." + " Expected at least two strings.");
}
// the first part is the annotation (@xxx)
String keyword = annotationParts[0];
AnnotatedStringHandler handler = getAnnotatedStringHandlerMap().get(keyword);
if (handler == null) {
return new InvalidAnnotatedStringHandler("Invalid annotation keyword: " + keyword);
}
return handler;
}
/**
* Returns all known annotation handlers
* @return the handlers
*/
public static List<AnnotatedStringHandler> getAnnotatedStringHandlers() {
if (ANNOTATED_STRING_HANDLERS == null) {
ANNOTATED_STRING_HANDLERS = getSupportedAnnotationHandlers();
}
return ANNOTATED_STRING_HANDLERS;
}
private static Map<String, AnnotatedStringHandler> getAnnotatedStringHandlerMap() {
if (ANNOTATED_STRING_MAP == null) { // lazy init due to our use of ClassSearcher
ANNOTATED_STRING_MAP = createAnnotatedStringHandlerMap();
}
return ANNOTATED_STRING_MAP;
}
private static Map<String, AnnotatedStringHandler> createAnnotatedStringHandlerMap() {
Map<String, AnnotatedStringHandler> map = new HashMap<>();
for (AnnotatedStringHandler instance : getAnnotatedStringHandlers()) {
String[] supportedAnnotations = instance.getSupportedAnnotations();
for (String supportedAnnotation : supportedAnnotations) {
map.put(supportedAnnotation, instance);
}
}
return Collections.unmodifiableMap(map);
}
// locates AnnotatedStringHandler implementations to handle annotations
private static List<AnnotatedStringHandler> getSupportedAnnotationHandlers() {
List<AnnotatedStringHandler> list = new ArrayList<>();
for (AnnotatedStringHandler h : ClassSearcher.getInstances(AnnotatedStringHandler.class)) {
if (h.getSupportedAnnotations().length != 0) {
list.add(h);
}
}
return Collections.unmodifiableList(list);
}
/*package*/ static Set<String> getAnnotationNames() {
return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet());
}
} }
@@ -388,9 +388,11 @@ public class EolCommentFieldFactory extends FieldFactory {
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0); RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
int encodedRow = startRowCol.row(); int encodedRow = startRowCol.row();
int encodedCol = startRowCol.col(); int encodedCol = startRowCol.col();
Annotation annotation = new Annotation(refAddrComment, currentPrefixString, program); Annotation annotation = new Annotation(refAddrComment, program);
FieldElement addressElement = FieldElement addressElement =
new AnnotatedTextFieldElement(annotation, encodedRow, encodedCol); new AnnotatedTextFieldElement(annotation, currentPrefixString, program, encodedRow,
encodedCol);
// Space character // Space character
AttributedString spaceStr = new AttributedString(" ", currentPrefixString.getColor(0), AttributedString spaceStr = new AttributedString(" ", currentPrefixString.getColor(0),
currentPrefixString.getFontMetrics(0), false, null); currentPrefixString.getFontMetrics(0), false, null);
@@ -15,15 +15,10 @@
*/ */
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import docking.widgets.fieldpanel.field.*;
public class StringCommentPart extends CommentPart { public class StringCommentPart extends CommentPart {
private AttributedString prototype; StringCommentPart(String text) {
StringCommentPart(String text, AttributedString prototype) {
super(text); super(text);
this.prototype = prototype;
} }
@Override @Override
@@ -31,12 +26,6 @@ public class StringCommentPart extends CommentPart {
return getDisplayText(); return getDisplayText();
} }
@Override
AbstractTextFieldElement createElement(int row, int column) {
AttributedString as = prototype.deriveAttributedString(displayText);
return new TextFieldElement(as, row, column);
}
@Override @Override
public String toString() { public String toString() {
return getDisplayText(); return getDisplayText();
@@ -15,20 +15,17 @@
*/ */
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.util.*; import java.util.List;
import java.util.regex.Pattern;
import docking.widgets.fieldpanel.field.AttributedString; import docking.widgets.fieldpanel.field.AttributedString;
import generic.theme.GThemeDefaults.Colors.Messages; import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.GThemeDefaults.Colors.Palette; import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.app.util.NamespaceUtils;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.Msg; import ghidra.util.Msg;
/** /**
@@ -43,32 +40,6 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
"@symbol annotation must have a valid " + "symbol name or address"; "@symbol annotation must have a valid " + "symbol name or address";
private static final String[] SUPPORTED_ANNOTATIONS = { "symbol", "sym" }; private static final String[] SUPPORTED_ANNOTATIONS = { "symbol", "sym" };
public static String convertAnnotationSymbolToAddress(String[] annotationParts, String rawText,
Program program) {
if (annotationParts.length <= 1) {
return null;
}
if (program == null) { // this can happen during merge operations
return null;
}
Address address = program.getAddressFactory().getAddress(annotationParts[1]);
if (address != null) {
return null; // nothing to do
}
String originalValue = annotationParts[1];
List<Symbol> symbols = getSymbols(originalValue, program);
if (symbols.size() != 1) {
// no unique symbol, so leave it as string name
return null;
}
Address symbolAddress = symbols.get(0).getAddress();
return rawText.replaceFirst(Pattern.quote(originalValue), symbolAddress.toString());
}
@Override @Override
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
Program program) { Program program) {
@@ -82,7 +53,7 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
return createUndecoratedString(prototypeString, text); return createUndecoratedString(prototypeString, text);
} }
List<Symbol> symbols = getSymbols(text[1], program); List<Symbol> symbols = CommentUtils.getSymbols(text[1], program);
// check for a symbol of the given name first // check for a symbol of the given name first
if (symbols.size() >= 1) { if (symbols.size() >= 1) {
@@ -106,25 +77,6 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
prototypeString.getFontMetrics(0)); prototypeString.getFontMetrics(0));
} }
private static List<Symbol> getSymbols(String rawText, Program program) {
List<Symbol> list = NamespaceUtils.getSymbols(rawText, program);
if (!list.isEmpty()) {
return list;
}
// if we get here, then see if the value is an address
Address address = program.getAddressFactory().getAddress(rawText);
if (address != null) {
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(address);
if (symbol != null) {
return Arrays.asList(symbol);
}
}
return Collections.emptyList();
}
@Override @Override
public String[] getSupportedAnnotations() { public String[] getSupportedAnnotations() {
return SUPPORTED_ANNOTATIONS; return SUPPORTED_ANNOTATIONS;
@@ -136,7 +88,7 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
String symbolText = annotationParts[1]; String symbolText = annotationParts[1];
Program program = sourceNavigatable.getProgram(); Program program = sourceNavigatable.getProgram();
List<Symbol> symbols = getSymbols(symbolText, program); List<Symbol> symbols = CommentUtils.getSymbols(symbolText, program);
GoToService goToService = serviceProvider.getService(GoToService.class); GoToService goToService = serviceProvider.getService(GoToService.class);
@@ -18,8 +18,9 @@ package ghidra.util.bean.field;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.RowColLocation; import docking.widgets.fieldpanel.support.RowColLocation;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.util.viewer.field.Annotation; import ghidra.app.util.viewer.field.*;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
/** /**
@@ -41,34 +42,51 @@ final public class AnnotatedTextFieldElement extends AbstractTextFieldElement {
/** /**
* Constructor that initializes this text field element with the given annotation and row * Constructor that initializes this text field element with the given annotation and row
* and column information. The text of this element is the text returned from * and column information. The text of this element is the display text created by the
* {@link Annotation#getDisplayString()}. * annotation handler for the given annotation.
* *
* @param annotation The Annotation that this element is describing. * @param annotation The Annotation that this element is describing.
* @param prototype the prototype string used to create new strings
* @param program the program
* @param row The row that this element is on * @param row The row that this element is on
* @param column The column value of this element (the column index where this element starts) * @param column The column value of this element (the column index where this element starts)
*/ */
public AnnotatedTextFieldElement(Annotation annotation, int row, int column) { public AnnotatedTextFieldElement(Annotation annotation, AttributedString prototype,
this(annotation, annotation.getDisplayString(), row, column); Program program, int row, int column) {
this(annotation, getDisplayString(annotation, prototype, program), row, column);
} }
/** /**
* Returns the original annotation text in the data model, which will differ from the display * Returns the original annotation text in the data model, which will differ from the display
* text. * text.
* @return the original annotation text in the data model. * @return the original annotation text in the data model.
* @see #getDisplayString()
*/ */
public String getRawText() { public String getRawText() {
return annotation.getAnnotationText(); return annotation.getAnnotationText();
} }
/**
* Returns the display string of annotation
* @return the display string
* @see #getRawText()
*/
public String getDisplayString() { public String getDisplayString() {
return annotation.getDisplayString().getText(); return attributedString.getText();
}
private static AttributedString getDisplayString(Annotation a, AttributedString prototype,
Program program) {
String[] annotationParts = a.getAnnotationParts();
AnnotatedStringHandler handler = CommentUtils.getAnnotationHandler(annotationParts);
AttributedString displayString;
try {
displayString = handler.createAnnotatedString(prototype, annotationParts, program);
}
catch (AnnotationException ae) {
// uh-oh
handler =
new InvalidAnnotatedStringHandler("Annotation Exception: " + ae.getMessage());
displayString = handler.createAnnotatedString(prototype, annotationParts, program);
}
return displayString;
} }
/** /**
@@ -81,7 +99,11 @@ final public class AnnotatedTextFieldElement extends AbstractTextFieldElement {
*/ */
public boolean handleMouseClicked(Navigatable sourceNavigatable, public boolean handleMouseClicked(Navigatable sourceNavigatable,
ServiceProvider serviceProvider) { ServiceProvider serviceProvider) {
return annotation.handleMouseClick(sourceNavigatable, serviceProvider);
String[] annotationParts = annotation.getAnnotationParts();
AnnotatedStringHandler handler = CommentUtils.getAnnotationHandler(annotationParts);
return handler.handleMouseClick(annotationParts, sourceNavigatable,
serviceProvider);
} }
@Override @Override
@@ -113,4 +135,5 @@ final public class AnnotatedTextFieldElement extends AbstractTextFieldElement {
return new AnnotatedTextFieldElement(annotation, return new AnnotatedTextFieldElement(annotation,
attributedString.replaceAll(targets, replacement), row, column); attributedString.replaceAll(targets, replacement), row, column);
} }
} }
@@ -58,7 +58,19 @@
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2><A name="Flow_Chart_Layout"></A>Flow Chart Layout</H2>
<blockquote>
<P>This layout organizes the code blocks into a tree structure with each parent vertex in the
tree being centered over its children. Edges are routed orthongally with minimal edge
crossings.</P>
</blockquote>
<H2><A name="Flow_Chart_Layout_Left"></A>Flow Chart Layout (Left)</H2>
<blockquote>
<P>This layout is the same as the Flow Chart Layout, except parent nodes are place directly
above their left most child.</P>
</blockquote>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>Function Graph Plugin</I></P> <P class="providedbyplugin">Provided by: <I>Function Graph Plugin</I></P>
<BR> <BR>
@@ -911,13 +911,11 @@ class FGActionManager {
private List<ActionState<FGLayoutProvider>> createActionStates( private List<ActionState<FGLayoutProvider>> createActionStates(
List<FGLayoutProvider> layoutProviders) { List<FGLayoutProvider> layoutProviders) {
List<ActionState<FGLayoutProvider>> list = new ArrayList<>(); List<ActionState<FGLayoutProvider>> list = new ArrayList<>();
HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
for (FGLayoutProvider layout : layoutProviders) { for (FGLayoutProvider layout : layoutProviders) {
ActionState<FGLayoutProvider> layoutState = ActionState<FGLayoutProvider> layoutState =
new ActionState<>(layout.getLayoutName(), layout.getActionIcon(), layout); new ActionState<>(layout.getLayoutName(), layout.getActionIcon(), layout);
layoutState.setHelpLocation(layoutHelpLocation); layoutState.setHelpLocation(layout.getHelpLocation());
list.add(layoutState); list.add(layoutState);
} }
@@ -20,6 +20,7 @@ import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.graph.viewer.layout.LayoutProvider; import ghidra.graph.viewer.layout.LayoutProvider;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -44,4 +45,13 @@ public abstract class FGLayoutProvider implements LayoutProvider<FGVertex, FGEdg
public FGLayoutOptions createLayoutOptions(Options options) { public FGLayoutOptions createLayoutOptions(Options options) {
return null; return null;
} }
/**
* Returns the help location for this layout. Subclasses should override this method to give
* specific help for the graph layout performed by this provider.
* @return the help location for this layout
*/
public HelpLocation getHelpLocation() {
return new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
}
} }
@@ -21,6 +21,7 @@ import generic.theme.GIcon;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProviderExtensionPoint; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProviderExtensionPoint;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -51,4 +52,9 @@ public class FlowChartLayoutProvider extends FGLayoutProviderExtensionPoint {
return new FGFlowChartLayout(graph, false); return new FGFlowChartLayout(graph, false);
} }
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("FunctionGraphPlugin", "Flow_Chart_Layout");
}
} }
@@ -21,6 +21,7 @@ import generic.theme.GIcon;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProviderExtensionPoint; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProviderExtensionPoint;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -51,4 +52,8 @@ public class LeftAlignedFlowChartLayoutProvider extends FGLayoutProviderExtensio
return new FGFlowChartLayout(graph, true); return new FGFlowChartLayout(graph, true);
} }
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("FunctionGraphPlugin", "Flow_Chart_Layout_Left");
}
} }
@@ -20,6 +20,7 @@ import javax.swing.Icon;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -27,6 +28,9 @@ import ghidra.util.task.TaskMonitor;
* A layout provider that allows us to specify a Jung layout by name. * A layout provider that allows us to specify a Jung layout by name.
*/ */
public class JgtNamedLayoutProvider extends FGLayoutProvider { public class JgtNamedLayoutProvider extends FGLayoutProvider {
// layout algorithm categories
static final String MIN_CROSS = "Hierarchical MinCross";
static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross";
private String layoutName; private String layoutName;
@@ -62,4 +66,17 @@ public class JgtNamedLayoutProvider extends FGLayoutProvider {
public String toString() { public String toString() {
return layoutName; return layoutName;
} }
@Override
public HelpLocation getHelpLocation() {
// condense hierarchical action help to the top-level help description
String anchor = layoutName;
if (layoutName.contains(VERT_MIN_CROSS)) {
anchor = VERT_MIN_CROSS;
}
else if (layoutName.contains(MIN_CROSS)) {
anchor = MIN_CROSS;
}
return new HelpLocation("GraphServices", anchor);
}
} }
@@ -30,6 +30,7 @@ import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.layout.*; import ghidra.graph.viewer.layout.*;
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer; import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.util.HelpLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -374,4 +375,9 @@ public class TestFGLayoutProvider extends FGLayoutProvider {
//@formatter:on //@formatter:on
} }
} }
@Override
public HelpLocation getHelpLocation() {
return null;
}
} }
@@ -20,6 +20,7 @@ import javax.swing.Icon;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DecompilerNestedLayoutProvider extends FGLayoutProviderExtensionPoint { public class DecompilerNestedLayoutProvider extends FGLayoutProviderExtensionPoint {
@@ -56,4 +57,9 @@ public class DecompilerNestedLayoutProvider extends FGLayoutProviderExtensionPoi
return 200; // above the others return 200; // above the others
} }
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("FunctionGraphPlugin", "Nested_Code_Layout");
}
} }
@@ -111,7 +111,7 @@
<BLOCKQUOTE> <BLOCKQUOTE>
<BLOCKQUOTE> <BLOCKQUOTE>
<UL> <UL>
<LI><A name="Compact Hierarchical"/> <LI><A name="Compact_Hierarchical"/>
<B>Compact Hierarchical</B> is the <B>TidierTree Layout Algorithm</B>. It builds a tree <B>Compact Hierarchical</B> is the <B>TidierTree Layout Algorithm</B>. It builds a tree
structure and attempts to reduce horizontal space.</LI> structure and attempts to reduce horizontal space.</LI>