Skip to content

Commit 82f6ad7

Browse files
committed
dfmc-reader: fix bug in make-hash-literal
`#:foo:{\}` would crash the compiler with "element outside of range" unless the `}` delimiter occurred later in the same source record.
1 parent 1ae5512 commit 82f6ad7

File tree

5 files changed

+44
-14
lines changed

5 files changed

+44
-14
lines changed

documentation/library-reference/source/language-extensions/parser-expansions.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ gets transformed, setter-like, into::
1818

1919
The ``<text>`` part can be either *delimited* or *undelimited*. Undelimited
2020
text can contain anything but commas, semicolons, brackets of any kind, and
21-
whitespace. There is no ``\`` escape processing. All the following are valid::
21+
whitespace. All the following are valid::
2222

2323
#:http://opendylan.org/
2424
#:time:12:30am
@@ -51,3 +51,9 @@ An example parser:
5151
5252
If an appropriate function isn't defined, you get a standard unbound variable
5353
reference message indicating the # literal.
54+
55+
Note that there is no escape processing except that the end delimiter may be
56+
escaped with a backslash and *the escape character itself is not removed*. For
57+
example, ``#:file:"C:\foo\"`` is an error because the end delimiter is escaped
58+
and therefore the hash literal is unterminated. ``#:file:"C:\foo\""`` results
59+
in the literal string ``C:\foo\"`` being passed to the parser.

sources/dfmc/reader/interface.dylan

+5
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ define serious-program-warning <ratios-not-supported> (<invalid-token>)
109109
format-arguments token-string;
110110
end serious-program-warning;
111111

112+
define serious-program-warning <unterminated-parser-expansion>
113+
format-string "Unterminated parser expansion %s";
114+
format-arguments token-string;
115+
end serious-program-warning;
116+
112117
define serious-program-warning <invalid-end-of-input> (<reader-error>)
113118
format-string
114119
"Unexpected end of input encountered while reading form.";

sources/dfmc/reader/lexer.dylan

+21-11
Original file line numberDiff line numberDiff line change
@@ -1368,18 +1368,28 @@ define method make-hash-literal
13681368
let start-delimiter = delimiter;
13691369
let end-delimiter
13701370
= as(<integer>, $hash-data-end-delimiters[delimiter-index]);
1371-
let i :: <integer> = data-start + 1;
1372-
let char :: <integer> = 0;
1373-
while (((char := contents[i]) ~== end-delimiter)
1374-
| (char == end-delimiter & contents[i - 1] == $escape-code))
1375-
if (char == $newline-code)
1376-
lexer.line := lexer.line + 1;
1377-
lexer.line-start := i;
1371+
iterate loop (i = data-start + 1, prev-char = 0)
1372+
if (i >= length)
1373+
note(<unterminated-parser-expansion>,
1374+
source-location:
1375+
record-position-as-location
1376+
(source-location.source-location-record,
1377+
source-location.source-location-source-position),
1378+
token-string: extract-string(source-location));
1379+
else
1380+
let char :: <integer> = contents[i];
1381+
if (char == end-delimiter & (prev-char ~== $escape-code))
1382+
data := extract-string(source-location, start: data-start + 1, end: i);
1383+
lexer.posn := i + 1;
1384+
else
1385+
if (char == $newline-code)
1386+
lexer.line := lexer.line + 1;
1387+
lexer.line-start := i;
1388+
end;
1389+
loop(i + 1, char);
1390+
end;
13781391
end;
1379-
i := i + 1;
1380-
end;
1381-
data := extract-string(source-location, start: data-start + 1, end: i);
1382-
lexer.posn := i + 1;
1392+
end iterate;
13831393
else
13841394
// Read until whitespace or EOF
13851395
let i :: <integer> = data-start;

sources/dfmc/reader/tests/expressions-test-suite.dylan

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ define function verify-hash-literal-function-call
112112
assert-equal(arg1.fragment-value, arg);
113113
end function;
114114

115-
define test hash-literal-test ()
115+
define test hash-literal-ast-test ()
116116
verify-hash-literal-function-call("#:foo:bar", #"foo-parser", "bar");
117117
verify-hash-literal-function-call("#:foo:{\nbar\n}", #"foo-parser", "\nbar\n");
118118
end test;
@@ -136,5 +136,5 @@ define suite expressions-test-suite ()
136136
test binary->=-test;
137137
// This doesn't test &, | and := yet.
138138
test escaped-name-test;
139-
test hash-literal-test;
139+
test hash-literal-ast-test;
140140
end suite expressions-test-suite;

sources/dfmc/reader/tests/literal-test-suite.dylan

+9
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ define test vector-literal-test ()
229229
verify-presentation(f, "#[\"a\", b:]");
230230
end test vector-literal-test;
231231

232+
define test hash-literal-test ()
233+
// End delimiter is escaped so the hash literal is not terminated. This used
234+
// to crash the compiler, unless '}' appeared somewhere later in the source
235+
// record.
236+
let source = "#:foo:{\\}";
237+
assert-false(read-fragment(source));
238+
end test;
239+
232240
define suite literal-test-suite ()
233241
test binary-integer-literal-test;
234242
test boolean-literal-test;
@@ -243,4 +251,5 @@ define suite literal-test-suite ()
243251
test string-literal-test;
244252
test symbol-literal-test;
245253
test vector-literal-test;
254+
test hash-literal-test;
246255
end suite literal-test-suite;

0 commit comments

Comments
 (0)