From da15c4d259c67fc0a297fa08d2c16fd5abb425a9 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 13 Sep 2026 09:59:25 +0200 Subject: [PATCH] Add escape test coverage for strings as long as 128 bytes Extracted from https://github.com/ruby/erb/pull/141 and https://github.com/ruby/erb/pull/144 Co-Authored-By: Scott Myron --- test/erb/test_erb_escape.rb | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/erb/test_erb_escape.rb b/test/erb/test_erb_escape.rb index c351feb..e529943 100644 --- a/test/erb/test_erb_escape.rb +++ b/test/erb/test_erb_escape.rb @@ -58,6 +58,85 @@ def test_simd_coverage assert_equal '<' * 32, h('<' * 32) end + def test_html_escape_simd_block_boundary + # Ensure we only escape the characters that need to be escaped. + (0...128).each do |pos| + s = "a" * 128 + s[pos] = "<" + expected = "a" * pos + "<" + "a" * (128 - pos - 1) + assert_equal(expected, ERB::Util.html_escape(s), "escape at position #{pos}") + end + end + + HTML_ESCAPE_ENTITIES = {"'" => "'", '"' => """, "&" => "&", "<" => "<", ">" => ">"} + + def test_html_escape_simd_multiple_matches_per_block + chars = ["'", '"', '&', '<', '>'] + (0..15).each do |a| + (0..15).each do |b| + next if a == b + s = "a" * 32 + s[a] = chars[a % chars.size] + s[b] = chars[b % chars.size] + expected = Array.new(32, "a") + expected[a] = HTML_ESCAPE_ENTITIES[chars[a % chars.size]] + expected[b] = HTML_ESCAPE_ENTITIES[chars[b % chars.size]] + assert_equal(expected.join, ERB::Util.html_escape(s), "positions #{a}, #{b}") + end + end + end + + def test_html_escape_simd_tail_lengths + (1..128).each do |len| + (0...len).each do |pos| + s = "a" * len + s[pos] = ">" + expected = "a" * pos + ">" + "a" * (len - pos - 1) + assert_equal(expected, ERB::Util.html_escape(s), "len=#{len} pos=#{pos}") + end + end + end + + def test_html_escape_simd_wide_block_boundary + # Ensure a 64-byte-wide SIMD fast path correctly locates a match + # at every byte position, including the last byte of the block + # (which is special-cased in find_next_match_neon). + (0...128).each do |pos| + s = "a" * 128 + s[pos] = "<" + expected = "a" * pos + "<" + "a" * (128 - pos - 1) + assert_equal(expected, ERB::Util.html_escape(s), "escape at position #{pos}") + end + end + + def test_html_escape_simd_wide_block_multiple_matches + chars = ["'", '"', '&', '<', '>'] + boundary_positions = [0, 1, 15, 16, 17, 31, 32, 33, 47, 48, 49, 62, 63] + boundary_positions.each do |a| + boundary_positions.each do |b| + next if a == b + s = "a" * 64 + s[a] = chars[a % chars.size] + s[b] = chars[b % chars.size] + expected = Array.new(64, "a") + expected[a] = HTML_ESCAPE_ENTITIES[chars[a % chars.size]] + expected[b] = HTML_ESCAPE_ENTITIES[chars[b % chars.size]] + assert_equal(expected.join, ERB::Util.html_escape(s), "positions #{a}, #{b}") + end + end + end + + def test_html_escape_simd_wide_block_tail_lengths + ([*56..72] + [*120..136]).each do |len| + (0...len).each do |pos| + s = "a" * len + s[pos] = ">" + expected = "a" * pos + ">" + "a" * (len - pos - 1) + assert_equal(expected, ERB::Util.html_escape(s), "len=#{len} pos=#{pos}") + end + end + end + private def h(...)