diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java index 5cc895bf6d..1d2e3ed50c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java @@ -56,7 +56,7 @@ public class AddressAnnotatedStringHandler implements AnnotatedStringHandler { String addressText = address.toString(); if (text.length > 2) { // address and display text - StringBuffer buffer = new StringBuffer(); + StringBuilder buffer = new StringBuilder(); for (int i = 2; i < text.length; i++) { buffer.append(text[i]).append(" "); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/Annotation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/Annotation.java index 74ef0cadc6..bbad853450 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/Annotation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/Annotation.java @@ -150,7 +150,7 @@ public class Annotation { } private String[] parseAnnotationText(String theAnnotationText) { - StringBuffer buffer = new StringBuffer(theAnnotationText); + StringBuilder buffer = new StringBuilder(theAnnotationText); // strip off the brackets buffer.delete(0, 2); // remove '{' and '@' @@ -196,8 +196,12 @@ public class Annotation { return annotationText; } + @Override + public String toString() { + return annotationText; + } + /*package*/ static Set getAnnotationNames() { return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet()); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotationCommentPart.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotationCommentPart.java new file mode 100644 index 0000000000..1309f3ea58 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotationCommentPart.java @@ -0,0 +1,44 @@ +/* ### + * 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 docking.widgets.fieldpanel.field.AbstractTextFieldElement; +import ghidra.util.bean.field.AnnotatedTextFieldElement; + +public class AnnotationCommentPart extends CommentPart { + + private Annotation annotation; + + AnnotationCommentPart(String displayText, Annotation annotation) { + super(displayText); + this.annotation = annotation; + } + + @Override + String getRawText() { + return annotation.getAnnotationText(); + } + + @Override + AbstractTextFieldElement createElement(int row, int column) { + return new AnnotatedTextFieldElement(annotation, row, column); + } + + @Override + public String toString() { + return annotation.toString(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentPart.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentPart.java new file mode 100644 index 0000000000..7410ccddc9 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentPart.java @@ -0,0 +1,35 @@ +/* ### + * 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 docking.widgets.fieldpanel.field.AbstractTextFieldElement; + +public abstract class CommentPart { + + protected String displayText; + + CommentPart(String displayText) { + this.displayText = displayText; + } + + abstract AbstractTextFieldElement createElement(int row, int column); + + abstract String getRawText(); + + String getDisplayText() { + return displayText; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java index 2c47061664..85f56eb754 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java @@ -31,8 +31,6 @@ import generic.theme.Gui; import ghidra.program.model.listing.Program; import ghidra.util.StringUtilities; import ghidra.util.WordLocation; -import ghidra.util.bean.field.AnnotatedTextFieldElement; -import ghidra.util.exception.AssertException; public class CommentUtils { @@ -75,21 +73,10 @@ public class CommentUtils { }; StringBuilder buffy = new StringBuilder(); - List parts = + List parts = doParseTextIntoTextAndAnnotations(rawCommentText, symbolFixer, program, prototype); - for (Object part : parts) { - - if (part instanceof String) { - String s = (String) part; - buffy.append(s); - } - else if (part instanceof Annotation) { - Annotation a = (Annotation) part; - buffy.append(a.getAnnotationText()); - } - else { - throw new AssertException("Unhandled annotation piece: " + part); - } + for (CommentPart part : parts) { + buffy.append(part.getRawText()); } return buffy.toString(); } @@ -136,7 +123,7 @@ public class CommentUtils { Function noFixing = Function.identity(); return doParseTextForAnnotations(text, noFixing, program, prototypeString, row); } - + /** * Sanitizes the given text, removing or replacing illegal characters. *

@@ -175,25 +162,12 @@ public class CommentUtils { text = StringUtilities.convertTabsToSpaces(text); int column = 0; - List parts = + List parts = doParseTextIntoTextAndAnnotations(text, fixerUpper, program, prototype); List fields = new ArrayList<>(); - for (Object part : parts) { - - if (part instanceof String) { - String s = (String) part; - AttributedString as = prototype.deriveAttributedString(s); - fields.add(new TextFieldElement(as, row, column)); - column += s.length(); - } - else if (part instanceof Annotation) { - Annotation a = (Annotation) part; - fields.add(new AnnotatedTextFieldElement(a, row, column)); - column += a.getAnnotationText().length(); - } - else { - throw new AssertException("Unhandled annotation piece: " + part); - } + for (CommentPart part : parts) { + fields.add(part.createElement(row, column)); + column += part.getDisplayText().length(); } return new CompositeFieldElement(fields.toArray(new FieldElement[fields.size()])); @@ -204,21 +178,21 @@ public class CommentUtils { * an Annotation * * @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 prototype the prototype string that contains decoration attributes * @return a list that contains a mixture String or an Annotation entries */ - private static List doParseTextIntoTextAndAnnotations(String text, + private static List doParseTextIntoTextAndAnnotations(String text, Function fixerUpper, Program program, AttributedString prototype) { - List results = new ArrayList<>(); + List results = new ArrayList<>(); List annotations = getCommentAnnotations(text); if (annotations.isEmpty()) { - results.add(text); + String updatedText = removeEscapeChars(text); + results.add(new StringCommentPart(text, updatedText, prototype)); return results; } @@ -230,19 +204,23 @@ public class CommentUtils { if (offset != start) { // text between annotations String preceeding = text.substring(offset, start); - results.add(preceeding); + String updatedText = removeEscapeChars(preceeding); + results.add(new StringCommentPart(preceeding, updatedText, prototype)); } String annotationText = word.getWord(); - Annotation annotation = new Annotation(annotationText, prototype, program); + String updatedText = removeEscapeChars(annotationText); + Annotation annotation = new Annotation(updatedText, prototype, program); annotation = fixerUpper.apply(annotation); - results.add(annotation); + results.add(new AnnotationCommentPart(updatedText, annotation)); offset = start + annotationText.length(); } if (offset != text.length()) { // trailing text - results.add(text.substring(offset)); + String trailing = text.substring(offset); + String updatedText = removeEscapeChars(trailing); + results.add(new StringCommentPart(trailing, updatedText, prototype)); } return results; @@ -291,6 +269,23 @@ public class CommentUtils { //@formatter:on } + // remove any backslashes that escape special annotation characters, like '{' and '}' + private static String removeEscapeChars(String text) { + StringBuilder buffy = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\\') { + char next = i == text.length() ? '\0' : text.charAt(i + 1); + if (next == '{' || next == '}') { + continue; + } + } + buffy.append(c); + } + + return buffy.toString(); + } + /* * Starts at the given index and looks for the end an annotation, ignoring quoted text * and escaped characters along the way. The value returned is the index after the last diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/StringCommentPart.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/StringCommentPart.java new file mode 100644 index 0000000000..e94a17b7b6 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/StringCommentPart.java @@ -0,0 +1,50 @@ +/* ### + * 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 docking.widgets.fieldpanel.field.*; + +public class StringCommentPart extends CommentPart { + + private AttributedString prototype; + private String rawText; + + StringCommentPart(String rawText, AttributedString prototype) { + this(rawText, rawText, prototype); + } + + StringCommentPart(String rawText, String displayText, AttributedString prototype) { + super(displayText); + this.rawText = rawText; + this.prototype = prototype; + } + + @Override + String getRawText() { + return rawText; + } + + @Override + AbstractTextFieldElement createElement(int row, int column) { + AttributedString as = prototype.deriveAttributedString(displayText); + return new TextFieldElement(as, row, column); + } + + @Override + public String toString() { + return rawText; + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java index 35506562de..bb54810947 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/AnnotationTest.java @@ -201,14 +201,21 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest { public void testSymbolAnnotation_WithBracesInName_Escaped() { String rawComment = "This is a symbol {@sym mySym\\{0\\}} annotation"; String display = CommentUtils.getDisplayString(rawComment, program); - assertEquals("This is a symbol mySym\\{0\\} annotation", display); + assertEquals("This is a symbol mySym{0} annotation", display); + } + + @Test + public void testSymbolAnnotation_WithEscapedItemsOutsideOfAnnotation() { + String rawComment = "This is a foo\\} symbol {@sym mySym\\{0\\}} annotation \\{bar"; + String display = CommentUtils.getDisplayString(rawComment, program); + assertEquals("This is a foo} symbol mySym{0} annotation {bar", display); } @Test public void testSymbolAnnotation_FullyEscaped() { String rawComment = "This is a symbol \\{@sym bob\\} annotation"; String display = CommentUtils.getDisplayString(rawComment, program); - assertEquals(rawComment, display); + assertEquals("This is a symbol {@sym bob} annotation", display); } @Test diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/CommentUtilsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/CommentUtilsTest.java index b9d3ef0fc4..e0bdea773c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/CommentUtilsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/CommentUtilsTest.java @@ -106,11 +106,12 @@ public class CommentUtilsTest extends AbstractGhidraHeadlessIntegrationTest { assertEquals(1, annotations.size()); WordLocation word = annotations.get(0); assertEquals("{@symbol symbol\\{Name\\}}", word.getWord()); + } - + @Test public void testSanitize() { - + String comment = null; String sanitized = CommentUtils.sanitize(comment); assertNull(sanitized);