From ad70c5c4591c860fc67a1c51d3de0856fedc377c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 25 Mar 2026 16:44:38 +0100 Subject: [PATCH] Improve no Symbol leak tests to be more reliable --- test/integration/security_test.rb | 49 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/test/integration/security_test.rb b/test/integration/security_test.rb index f8405894..3a35d47c 100644 --- a/test/integration/security_test.rb +++ b/test/integration/security_test.rb @@ -44,32 +44,39 @@ class SecurityTest < Minitest::Test end def test_does_not_permanently_add_filters_to_symbol_table - current_symbols = Symbol.all_symbols + assert_no_new_symbols do + # MRI imprecisely marks objects found on the C stack, which can result + # in uninitialized memory being marked. This can even result in the test failing + # deterministically for a given compilation of ruby. Using a separate thread will + # keep these writes of the symbol pointer on a separate stack that will be garbage + # collected after Thread#join. + Thread.new do + test = %( {{ "some_string" | a_bad_filter }} ) + Template.parse(test).render! + nil + end.join - # MRI imprecisely marks objects found on the C stack, which can result - # in uninitialized memory being marked. This can even result in the test failing - # deterministically for a given compilation of ruby. Using a separate thread will - # keep these writes of the symbol pointer on a separate stack that will be garbage - # collected after Thread#join. - Thread.new do - test = %( {{ "some_string" | a_bad_filter }} ) - Template.parse(test).render! - nil - end.join - - GC.start - - assert_equal([], Symbol.all_symbols - current_symbols) + GC.start + end end def test_does_not_add_drop_methods_to_symbol_table + assert_no_new_symbols do + assigns = { 'drop' => Drop.new } + assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!) + assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!) + assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!) + end + end + + def assert_no_new_symbols + # Run once to trigger any first-time initialization which might create some symbols, + # for example autoload or lazy method parsing might create symbols on first execution. + yield + + # Ensure no new symbols for further runs, i.e. the code does not leak symbols current_symbols = Symbol.all_symbols - - assigns = { 'drop' => Drop.new } - assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!) - assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!) - assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!) - + yield assert_equal([], Symbol.all_symbols - current_symbols) end