Skip to content

Commit 5eae0c5

Browse files
committed
Validate folded template sources
Signed-off-by: Christoph Knittel <ck@cca.io>
1 parent a811a5b commit 5eae0c5

3 files changed

Lines changed: 57 additions & 28 deletions

File tree

compiler/core/js_exp_make.ml

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,38 +89,25 @@ let interpolated_template ?comment segments values : t =
8989
Some ({source; semantic} : Asttypes.template_segment)
9090
| _ -> None
9191
in
92-
let append_source left right =
93-
let left_length = String.length left in
94-
let rec count_backslashes index count =
95-
if index >= 0 && String.unsafe_get left index = '\\' then
96-
count_backslashes (index - 1) (count + 1)
97-
else count
98-
in
99-
if
100-
left_length > 0 && right <> ""
101-
&& String.unsafe_get left (left_length - 1) = '$'
102-
&& String.unsafe_get right 0 = '{'
103-
&& count_backslashes (left_length - 2) 0 mod 2 = 0
104-
then String.sub left 0 (left_length - 1) ^ {e|\$|e} ^ right
105-
else left ^ right
106-
in
10792
let rec merge rev_segments rev_values
10893
(segments : Asttypes.template_segment list) values =
10994
match (segments, values) with
11095
| [segment], [] -> (List.rev (segment :: rev_segments), List.rev rev_values)
11196
| segment :: next_segment :: rest, value :: values -> (
11297
match literal_segment value with
11398
| Some literal ->
114-
let merged : Asttypes.template_segment =
115-
{
116-
source =
117-
append_source
118-
(append_source segment.source literal.source)
119-
next_segment.source;
120-
semantic =
121-
segment.semantic ^ literal.semantic ^ next_segment.semantic;
122-
}
99+
let semantic =
100+
segment.semantic ^ literal.semantic ^ next_segment.semantic
101+
in
102+
let preserved_source =
103+
segment.source ^ literal.source ^ next_segment.source
104+
in
105+
let source =
106+
match String_literal.decode_js_template_escapes preserved_source with
107+
| Some decoded when decoded = semantic -> preserved_source
108+
| _ -> String_literal.encode_js_template semantic
123109
in
110+
let merged : Asttypes.template_segment = {source; semantic} in
124111
merge rev_segments rev_values (merged :: rest) values
125112
| None ->
126113
merge (segment :: rev_segments) (value :: rev_values)

compiler/ml/string_literal.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ let decode_js_escapes_with ~normalize_template_line_endings s =
154154
Buffer.add_char buf '\n';
155155
if index + 1 < len && s.[index + 1] = '\n' then loop (index + 2)
156156
else loop (index + 1)
157+
| '$'
158+
when normalize_template_line_endings
159+
&& index + 1 < len
160+
&& s.[index + 1] = '{' ->
161+
(* An unescaped interpolation opener cannot occur inside one template
162+
segment. Treat it as invalid so callers can safely validate joined
163+
segment sources with this decoder. *)
164+
None
157165
| _ -> (
158166
match copy_utf8 index with
159167
| Some next -> loop next

tests/ounit_tests/ounit_string_literal_tests.ml

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ let assert_template_decoded ~encoded ~expected =
1010
OUnit.assert_equal ~printer:Ext_obj.dump (Some expected)
1111
(String_literal.decode_js_template_escapes encoded)
1212

13+
let assert_invalid_template encoded =
14+
OUnit.assert_equal ~printer:Ext_obj.dump None
15+
(String_literal.decode_js_template_escapes encoded)
16+
1317
let assert_encoded ~semantic ~expected =
1418
let encoded = String_literal.encode_js_string semantic in
1519
OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected encoded;
@@ -391,6 +395,9 @@ let suites =
391395
OUnit.assert_equal ~printer:Ext_obj.dump None
392396
(String_literal.decode_js_template_escapes encoded))
393397
[{|a\1b|}; {|a\01b|}; {|a\8b|}] );
398+
( "template segments reject interpolation openers" >:: fun _ ->
399+
assert_invalid_template "${value}";
400+
assert_template_decoded ~encoded:"\\${value}" ~expected:"${value}" );
394401
( "ordinary literals become semantic strings" >:: fun _ ->
395402
assert_parsed_string ~source:{|\x61\n\uD83D\uDE00|}
396403
~expected_semantic:"a\n😀" );
@@ -695,15 +702,42 @@ let suites =
695702
OUnit.assert_equal ~printer:(Printf.sprintf "%S") {e|`\${x}`|e}
696703
(Js_dump.string_of_expression boundary);
697704
let already_escaped_boundary =
705+
Js_exp_make.interpolated_template
706+
[{source = "\\$"; semantic = "$"}; {source = ""; semantic = ""}]
707+
[Js_exp_make.template_literal ~semantic:"{x}" "{x}"]
708+
in
709+
OUnit.assert_equal ~printer:(Printf.sprintf "%S") {e|`\${x}`|e}
710+
(Js_dump.string_of_expression already_escaped_boundary);
711+
let null_digit_boundary =
698712
Js_exp_make.interpolated_template
699713
[
700-
{source = {e|\$|e}; semantic = "$"};
714+
{source = "\\0"; semantic = "\000"};
701715
{source = ""; semantic = ""};
702716
]
703-
[Js_exp_make.template_literal ~semantic:"{x}" "{x}"]
717+
[Js_exp_make.template_literal ~semantic:"1" "1"]
704718
in
705-
OUnit.assert_equal ~printer:(Printf.sprintf "%S") {e|`\${x}`|e}
706-
(Js_dump.string_of_expression already_escaped_boundary) );
719+
OUnit.assert_equal ~printer:(Printf.sprintf "%S") {e|`\x001`|e}
720+
(Js_dump.string_of_expression null_digit_boundary);
721+
let escaped_slash_null_boundary =
722+
Js_exp_make.interpolated_template
723+
[
724+
{source = "\\\\0"; semantic = "\\0"};
725+
{source = ""; semantic = ""};
726+
]
727+
[Js_exp_make.template_literal ~semantic:"1" "1"]
728+
in
729+
OUnit.assert_equal ~printer:(Printf.sprintf "%S") {e|`\\01`|e}
730+
(Js_dump.string_of_expression escaped_slash_null_boundary);
731+
let line_ending_boundary =
732+
Js_exp_make.interpolated_template
733+
[
734+
{source = "a\r"; semantic = "a\n"};
735+
{source = "\nb"; semantic = "\nb"};
736+
]
737+
[Js_exp_make.template_literal ~semantic:"" ""]
738+
in
739+
OUnit.assert_equal ~printer:(Printf.sprintf "%S") {e|`a\n\nb`|e}
740+
(Js_dump.string_of_expression line_ending_boundary) );
707741
( "JavaScript references are not encoded as strings" >:: fun _ ->
708742
let value = Js_exp_make.var (Ext_ident.create "value") in
709743
(match (Js_exp_make.is_array value).expression_desc with

0 commit comments

Comments
 (0)