Skip to content

Commit 219c6ac

Browse files
reid-spencerclaude
andcommitted
Fix crash in error annotator when parser fails at EOF
A parse failure with a missing closing `}` at EOF (e.g., `context X is { ... ` with no `}` before EOF) constructs an `At` whose `endOffset` is one past the end of the last line computed by `lineRangeOf`. Two `require` calls in `RiddlParserInput.annotateErrorLine` then threw `IllegalArgumentException: requirement failed: fail: 58 >= 59`, which the outer `runMain` catch wrapped as a SEVERE "Exception Thrown" message, hiding the real error. The downstream slicing already clamps via `Math.min(..., data.length)`, so those requires were over-strict, not load-bearing. Removed them with a comment explaining the boundary condition. After the fix, a missing-brace input produces a normal Error with file location, e.g.: [error] badEntity.riddl(5:1->6:2): Expected one of ("..." | "}" | ...) Regression test added at `TopLevelParserTest`. Fixture files `language/input/riddl-bad/{badDomain,badEntity}.riddl` are checked in (3 opens, 2 closes). Test asserts no Severe and that the resulting Error mentions the file and lists `}` in the expected-tokens set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a89daa6 commit 219c6ac

4 files changed

Lines changed: 48 additions & 2 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
domain BadDomain is {
2+
include "badEntity.riddl"
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
context BadContext is {
2+
entity BadEntity is {
3+
???
4+
}
5+

language/jvm-native/src/test/scala/com/ossuminc/riddl/language/parsing/TopLevelParserTest.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,39 @@ class TopLevelParserTest extends ParsingTest {
147147
case Right(result) => result.contents.length must be(3)
148148
}
149149
}
150+
// Regression: a missing closing brace at EOF previously crashed the
151+
// error reporter (`annotateErrorLine`) with
152+
// `IllegalArgumentException: requirement failed: fail: 58 >= 59`,
153+
// which the outer driver caught and surfaced as a SEVERE
154+
// "Exception Thrown" message instead of a normal Error with the
155+
// offending file/line. Fixture files are at
156+
// `language/input/riddl-bad/{badDomain,badEntity}.riddl` and
157+
// intentionally have 3 opens / 2 closes.
158+
"report a normal Error (not Severe) when closing brace is missing at EOF" in {
159+
(_: TestData) =>
160+
val url: URL = PathUtils.urlFromCwdPath(
161+
Path.of(testInput + "/riddl-bad/badDomain.riddl")
162+
)
163+
val future = TopLevelParser.parseURL(url)
164+
Await.result(future, 10.seconds) match
165+
case Right(_) =>
166+
fail("Expected the malformed input to fail parsing")
167+
case Left(messages: Messages) =>
168+
// No Severe / "Exception Thrown" — the error reporter
169+
// must not crash on the EOF boundary condition.
170+
val severes = messages.filter(_.kind == SevereError)
171+
withClue(s"Got severe messages: ${severes.map(_.format).mkString(", ")}\n") {
172+
severes mustBe empty
173+
}
174+
// We do expect an Error from fastparse listing `}` as one
175+
// of the expected tokens, located in badEntity.riddl.
176+
val errors = messages.filter(_.kind == Error)
177+
errors mustNot be(empty)
178+
val combined = errors.map(_.format).mkString("\n")
179+
combined must include("badEntity.riddl")
180+
combined must include("Expected")
181+
combined must include("}")
182+
end match
183+
}
150184
}
151185
}

language/shared/src/main/scala/com/ossuminc/riddl/language/parsing/RiddlParserInput.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,12 @@ abstract class RiddlParserInput(using pc: PlatformContext) extends ParserInput {
202202
s"${index.endOffset} >= 0 && ${index.endOffset} <= ${data.length + 1}"
203203
)
204204
val (start, end) = lineRangeOf(index)
205-
require(start <= index.offset, s"fail: $start <= ${index.offset}")
206-
require(end >= index.endOffset, s"fail: $end >= ${index.endOffset}")
205+
// When a parse failure occurs at EOF (e.g., missing closing `}`),
206+
// the failure's `At` can have `endOffset` past the end of the line
207+
// computed by `lineRangeOf`. The original `require` would crash the
208+
// error reporter itself. Downstream slicing already clamps via
209+
// `Math.min`, so this defensive code can tolerate the boundary
210+
// condition instead of failing.
207211
val quoted = slice(start, end)
208212
if quoted.isEmpty then ""
209213
else {

0 commit comments

Comments
 (0)