diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/parse/AssemblyParseErrorResult.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/parse/AssemblyParseErrorResult.java index 75125a7cd5..58a39cb1d9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/parse/AssemblyParseErrorResult.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/parse/AssemblyParseErrorResult.java @@ -15,13 +15,20 @@ */ package ghidra.app.plugin.assembler.sleigh.parse; -import java.util.Collections; -import java.util.Set; +import java.util.*; + +import org.apache.commons.collections4.IterableUtils; /** * An unsuccessful result from parsing */ public class AssemblyParseErrorResult extends AssemblyParseResult { + /** + * The maximum number of suggestions to print when describing this error, e.g., when reported in + * exception messages. + */ + private static final int SUGGESTIONS_THRESHOLD = 10; + private final String buffer; private final Set suggestions; @@ -40,15 +47,28 @@ public class AssemblyParseErrorResult extends AssemblyParseResult { /** * Get a description of the error + * * @return a description */ public String describeError() { - return "Syntax Error: Expected " + suggestions + ". Got " + buffer; + Collection truncSuggestions; + if (suggestions.size() <= SUGGESTIONS_THRESHOLD) { + truncSuggestions = suggestions; + } + else { + truncSuggestions = new ArrayList<>(); + for (String s : IterableUtils.boundedIterable(suggestions, SUGGESTIONS_THRESHOLD)) { + truncSuggestions.add(s); + } + truncSuggestions.add("..."); + } + return "Syntax Error: Expected " + truncSuggestions + ". Got " + buffer; } /** * Get a set of suggested tokens that would have allowed parsing to continue - * @return the set + * + * @return the token set */ public Set getSuggestions() { return Collections.unmodifiableSet(suggestions); @@ -56,7 +76,8 @@ public class AssemblyParseErrorResult extends AssemblyParseResult { /** * Get the leftover contents of the input buffer when the error occurred - * @return + * + * @return the remaining buffer contents */ public String getBuffer() { return buffer; diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java index 4f39b056a2..77914b0229 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java @@ -202,7 +202,7 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { long addr, String ctxstr) { final AssemblyPatternBlock ctx = (ctxstr == null ? context.getDefault() : AssemblyPatternBlock.fromString(ctxstr)) - .fillMask(); + .fillMask(); dbg.println("Checking each: " + disassembly + " ctx:" + ctx); boolean gotOne = false; boolean failedOne = false; @@ -303,7 +303,7 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { /** * Run a test with the given checks * - * @param assembly the text to assembly + * @param assembly the text to assemble * @param instr an instruction pattern that must appear in the results * @param disassembly a set of acceptable disassembly texts * @param addr the address for assembly and disassembly @@ -326,7 +326,7 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { } final AssemblyPatternBlock ctx = (ctxstr == null ? context.getDefault() : AssemblyPatternBlock.fromString(ctxstr)) - .fillMask(); + .fillMask(); try { String disstr; PseudoInstruction psins = disassemble(addr, ins.getVals(), ctx.getVals()); @@ -398,7 +398,7 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { /** * Run a test where one result must match a given instruction pattern, and all others must * disassemble exactly to the input - * + * * @param assembly the input assembly * @param instr the instruction pattern * @see AssemblyPatternBlock#fromString(String) diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java index a52b4d3f6b..b080127dd2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java @@ -15,6 +15,9 @@ */ package ghidra.app.plugin.assembler.sleigh; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import org.junit.Test; import ghidra.app.plugin.assembler.*; @@ -31,6 +34,20 @@ public class x86AssemblyTest extends AbstractAssemblyTest { return new LanguageID("x86:LE:64:default"); } + @Test + public void testReasonableErrorMessageLength() throws AssemblySemanticException { + Assembler assembler = Assemblers.getAssembler(lang); + Address addr = lang.getDefaultSpace().getAddress(DEFAULT_ADDR); + try { + assembler.assembleLine(addr, "UNLIKELY qword ptr [RAX],RBX"); + fail(); // The exception must be thrown + } + catch (AssemblySyntaxException e) { + Msg.info(this, "Got expected syntax error: " + e); + assertTrue(e.getMessage().length() < 1000); + } + } + @Test public void testAssemble_ADD_m0x12_RAXm_RBX() { // Again, a little odd. Imm8 does not have the I+R form.