Describe the bug
Under certain circumstances, triple-quoted strings created with the ~b containing hexadecimal escape sequences seem to be incorrectly compiled. More specifically, it seems that the leading \ of the escape sequence is lost, causing the x{XXXX} to appear verbatim in the binary.
This seems related to #11365 , except that it affect compiled module instead of functions defined in the shell.
I have assembled a few repro modules, but I have not been able to pin down exactly what triggers the issue. The exact same triple-quoted string in two different modules, or even within the same module, might or might not exhibit the issue, depending on the content of the module.
Note: I have not tested the behaviour for other (or no) sigils. Other sigils producing binaries do not respect escape sequence in triple-quoted strings.
To Reproduce
There are 4 files. All files compile successfully. Functions return a binary. There are eunit tests at the bottom of each file.
triple_quote_ok: all as expected.
triple_quote_ok2: all as expected.
triple_quote_fail1: the same file as triple_quote_ok2, but with two additional functions triple_quote5/0 and triple_quote6/0. Two failing tests: triple_quote1_test and triple_quote6_test.
triple_quote_fail2: the same file as triple_quote_fail1, but with an additional function triple_quote4a/0. One failing test: triple_quote5_test. triple_quote1_test and triple_quote6_test do not fail in this module.
-module(triple_quote_ok).
-include_lib("eunit/include/eunit.hrl").
-export([
expected/0,
triple_quote/0
]).
expected() ->
String = "__\x{200C}\x{06DD}\x{070F}\x{180E}__",
unicode:characters_to_binary(String).
triple_quote() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}__
""".
triple_quote_test() ->
?assertEqual(expected(), triple_quote()).
-module(triple_quote_ok2).
-include_lib("eunit/include/eunit.hrl").
-export([
expected/0,
triple_quote1/0,
triple_quote2/0,
triple_quote3/0,
triple_quote4/0
]).
expected() ->
String = "__\x{200C}\x{06DD}\x{070F}\x{180E}\x{206F}__",
unicode:characters_to_binary(String).
triple_quote1() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}__
""".
triple_quote2() ->
~b"""
__\x{200C}__
""".
triple_quote3() ->
~b"""
__\x{200C}\x{06DD}__
""".
triple_quote4() ->
~b"""
__\x{200C}\x{06DD}\x{070F}__
""".
triple_quote1_test() ->
String = triple_quote1(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote2_test() ->
String = triple_quote2(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote3_test() ->
String = triple_quote3(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote4_test() ->
String = triple_quote4(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
-module(triple_quote_fail1).
-include_lib("eunit/include/eunit.hrl").
-export([
expected/0,
triple_quote1/0,
triple_quote2/0,
triple_quote3/0,
triple_quote4/0,
triple_quote5/0,
triple_quote6/0
]).
expected() ->
String = "__\x{200C}\x{06DD}\x{070F}\x{180E}\x{206F}__",
unicode:characters_to_binary(String).
triple_quote1() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}__
""".
triple_quote2() ->
~b"""
__\x{200C}__
""".
triple_quote3() ->
~b"""
__\x{200C}\x{06DD}__
""".
triple_quote4() ->
~b"""
__\x{200C}\x{06DD}\x{070F}__
""".
triple_quote5() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}__
""".
triple_quote6() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}\x{206F}__
""".
triple_quote1_test() ->
String = triple_quote1(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote2_test() ->
String = triple_quote2(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote3_test() ->
String = triple_quote3(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote4_test() ->
String = triple_quote4(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote5_test() ->
String = triple_quote5(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote6_test() ->
?assertEqual(expected(), triple_quote6()).
-module(triple_quote_fail2).
-include_lib("eunit/include/eunit.hrl").
-export([
expected/0,
triple_quote1/0,
triple_quote2/0,
triple_quote3/0,
triple_quote4/0,
triple_quote4a/0,
triple_quote5/0,
triple_quote6/0
]).
expected() ->
String = "__\x{200C}\x{06DD}\x{070F}\x{180E}\x{206F}__",
unicode:characters_to_binary(String).
triple_quote1() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}__
""".
triple_quote2() ->
~b"""
__\x{200C}__
""".
triple_quote3() ->
~b"""
__\x{200C}\x{06DD}__
""".
triple_quote4a() ->
~b"""
__\x{200C}\x{06DD}\x{070F}__
""".
triple_quote4() ->
~b"""
__\x{200C}\x{06DD}\x{070F}__
""".
triple_quote5() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}__
""".
triple_quote6() ->
~b"""
__\x{200C}\x{06DD}\x{070F}\x{180E}\x{206F}__
""".
triple_quote1_test() ->
String = triple_quote1(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote2_test() ->
String = triple_quote2(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote3_test() ->
String = triple_quote3(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote4_test() ->
String = triple_quote4(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote5_test() ->
String = triple_quote5(),
ExpectedPrefix = byte_size(String) -2,
?assertEqual(ExpectedPrefix, binary:longest_common_prefix([String, expected()])).
triple_quote6_test() ->
?assertEqual(expected(), triple_quote6()).
Output:
Eshell V16.3 (press Ctrl+G to abort, type help(). for help)
1> c(triple_quote_ok).
{ok,triple_quote_ok}
2> c(triple_quote_ok2).
{ok,triple_quote_ok2}
3> c(triple_quote_fail1).
{ok,triple_quote_fail1}
4> c(triple_quote_fail2).
{ok,triple_quote_fail2}
5> eunit:test(triple_quote_ok).
Test passed.
ok
6> eunit:test(triple_quote_ok2).
All 4 tests passed.
ok
7> eunit:test(triple_quote_fail1).
triple_quote_fail1: triple_quote1_test...*failed*
in function triple_quote_fail1:triple_quote1_test/0 (triple_quote_fail1.erl, line 52)
in call from eunit_test:'-mf_wrapper/2-fun-0-'/2 (eunit_test.erl, line 285)
in call from eunit_test:run_testfun/1 (eunit_test.erl, line 83)
in call from eunit_proc:run_test/1 (eunit_proc.erl, line 554)
in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 379)
in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 537)
in call from eunit_proc:tests_inorder/3 (eunit_proc.erl, line 479)
in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 369)
**error:{assertEqual,[{module,triple_quote_fail1},
{line,52},
{expression,"binary : longest_common_prefix ( [ String , expected ( ) ] )"},
{expected,16},
{value,2}]}
output:<<"">>
triple_quote_fail1: triple_quote6_test...*failed*
in function triple_quote_fail1:triple_quote6_test/0 (triple_quote_fail1.erl, line 75)
in call from eunit_test:'-mf_wrapper/2-fun-0-'/2 (eunit_test.erl, line 285)
in call from eunit_test:run_testfun/1 (eunit_test.erl, line 83)
in call from eunit_proc:run_test/1 (eunit_proc.erl, line 554)
in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 379)
in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 537)
in call from eunit_proc:tests_inorder/3 (eunit_proc.erl, line 479)
in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 369)
**error:{assertEqual,[{module,triple_quote_fail1},
{line,75},
{expression,"triple_quote6 ( )"},
{expected,<<95,95,226,128,140,219,157,220,143,225,160,...>>},
{value,<<95,95,226,128,140,219,157,220,143,225,...>>}]}
output:<<"">>
=======================================================
Failed: 2. Skipped: 0. Passed: 4.
error
8> eunit:test(triple_quote_fail2).
triple_quote_fail2: triple_quote5_test...*failed*
in function triple_quote_fail2:triple_quote5_test/0 (triple_quote_fail2.erl, line 78)
in call from eunit_test:'-mf_wrapper/2-fun-0-'/2 (eunit_test.erl, line 285)
in call from eunit_test:run_testfun/1 (eunit_test.erl, line 83)
in call from eunit_proc:run_test/1 (eunit_proc.erl, line 554)
in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 379)
in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 537)
in call from eunit_proc:tests_inorder/3 (eunit_proc.erl, line 479)
in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 369)
**error:{assertEqual,[{module,triple_quote_fail2},
{line,78},
{expression,"binary : longest_common_prefix ( [ String , expected ( ) ] )"},
{expected,17},
{value,7}]}
output:<<"">>
=======================================================
Failed: 1. Skipped: 0. Passed: 5.
error
9> {triple_quote_ok2:triple_quote1(), triple_quote_fail1:triple_quote1(), triple_quote_fail2:triple_quote1()}.
{<<95,95,226,128,140,219,157,220,143,225,160,142,95,95>>,
<<95,95,120,123,50,48,48,67,125,219,157,220,143,225,160,
142,95,95>>,
<<95,95,226,128,140,219,157,220,143,225,160,142,95,95>>}
10> binary:part(triple_quote_fail1:triple_quote1(), 0, 9).
<<"__x{200C}">>
Expected behavior
Triple-quoted string behave as documented: when using the ~b sigil, escape sequences are in effect.
Affected versions
Tested on 28.4.1 and 29.0.2.
Describe the bug
Under certain circumstances, triple-quoted strings created with the
~bcontaining hexadecimal escape sequences seem to be incorrectly compiled. More specifically, it seems that the leading\of the escape sequence is lost, causing thex{XXXX}to appear verbatim in the binary.This seems related to #11365 , except that it affect compiled module instead of functions defined in the shell.
I have assembled a few repro modules, but I have not been able to pin down exactly what triggers the issue. The exact same triple-quoted string in two different modules, or even within the same module, might or might not exhibit the issue, depending on the content of the module.
Note: I have not tested the behaviour for other (or no) sigils. Other sigils producing binaries do not respect escape sequence in triple-quoted strings.
To Reproduce
There are 4 files. All files compile successfully. Functions return a binary. There are
eunittests at the bottom of each file.triple_quote_ok: all as expected.triple_quote_ok2: all as expected.triple_quote_fail1: the same file astriple_quote_ok2, but with two additional functionstriple_quote5/0andtriple_quote6/0. Two failing tests:triple_quote1_testandtriple_quote6_test.triple_quote_fail2: the same file astriple_quote_fail1, but with an additional functiontriple_quote4a/0. One failing test:triple_quote5_test.triple_quote1_testandtriple_quote6_testdo not fail in this module.Output:
Expected behavior
Triple-quoted string behave as documented: when using the
~bsigil, escape sequences are in effect.Affected versions
Tested on 28.4.1 and 29.0.2.