Interpolated strings must not be frozen

Strings concatenated with backslash may end up being frozen when they
shouldn't be.  This commit fixes the issue.  It required a change
upstream in Prism, but also a change to the Prism compiler in CRuby.

  https://github.com/ruby/prism/pull/3606

[Bug #21187]
This commit is contained in:
Aaron Patterson 2025-07-21 17:13:44 -07:00 committed by Aaron Patterson
parent 0f408602cb
commit a1403fb7cb
3 changed files with 15 additions and 6 deletions

View file

@ -5279,6 +5279,10 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_
switch (PM_NODE_TYPE(part)) {
case PM_STRING_NODE:
// If inner string is not frozen, clear flags for this string
if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) {
CLEAR_FLAGS(node);
}
part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE);
break;
case PM_INTERPOLATED_STRING_NODE:

View file

@ -556,7 +556,7 @@ parse_regexp_concat(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm
static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node);
static int
pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding)
pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding, bool mutable_result)
{
int stack_size = 0;
size_t parts_size = parts->size;
@ -666,7 +666,7 @@ pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const
if (RTEST(current_string)) {
current_string = rb_fstring(current_string);
if (stack_size == 0 && interpolated) {
if (stack_size == 0 && (interpolated || mutable_result)) {
PUSH_INSN1(ret, current_location, putstring, current_string);
}
else {
@ -690,7 +690,7 @@ pm_compile_regexp_dynamic(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_
rb_encoding *explicit_regexp_encoding = parse_regexp_encoding(scope_node, node);
rb_encoding *implicit_regexp_encoding = explicit_regexp_encoding != NULL ? explicit_regexp_encoding : scope_node->encoding;
int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding);
int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding, false);
PUSH_INSN2(ret, *node_location, toregexp, INT2FIX(parse_regexp_flags(node) & 0xFF), INT2FIX(length));
}
@ -9637,7 +9637,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
}
else {
const pm_interpolated_string_node_t *cast = (const pm_interpolated_string_node_t *) node;
int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL);
int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, !PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN));
if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length));
if (popped) PUSH_INSN(ret, location, pop);
}
@ -9648,7 +9648,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
// :"foo #{bar}"
// ^^^^^^^^^^^^^
const pm_interpolated_symbol_node_t *cast = (const pm_interpolated_symbol_node_t *) node;
int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL);
int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, false);
if (length > 1) {
PUSH_INSN1(ret, location, concatstrings, INT2FIX(length));
@ -9670,7 +9670,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
PUSH_INSN(ret, location, putself);
int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL);
int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL, false);
if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length));
PUSH_SEND_WITH_FLAG(ret, location, idBackquote, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE));

View file

@ -4,6 +4,11 @@ require_relative "../test_helper"
module Prism
class StaticLiteralsTest < TestCase
def test_concatenanted_string_literal_is_not_static
node = Prism.parse_statement("'a' 'b'")
refute_predicate node, :static_literal?
end
def test_static_literals
assert_warning("1")
assert_warning("0xA", "10", "10")