From 30038656aa3a4931abefd99b8166c26599d168fd Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 4 Oct 2024 13:57:14 -0400 Subject: [PATCH] Fix intermediate array off-by-one error Co-authored-by: Adam Hess --- iseq.c | 51 ++++++++++++++++++++++++++++++--- prism_compile.c | 2 ++ test/ruby/test_compile_prism.rb | 5 +++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/iseq.c b/iseq.c index 3d18fb597a..a60228c8e5 100644 --- a/iseq.c +++ b/iseq.c @@ -1569,6 +1569,48 @@ iseqw_s_compile(int argc, VALUE *argv, VALUE self) return iseqw_s_compile_parser(argc, argv, self, rb_ruby_prism_p()); } +/* + * call-seq: + * InstructionSequence.compile_parsey(source[, file[, path[, line[, options]]]]) -> iseq + * + * Takes +source+, which can be a string of Ruby code, or an open +File+ object. + * that contains Ruby source code. It parses and compiles using parse.y. + * + * Optionally takes +file+, +path+, and +line+ which describe the file path, + * real path and first line number of the ruby code in +source+ which are + * metadata attached to the returned +iseq+. + * + * +file+ is used for `__FILE__` and exception backtrace. +path+ is used for + * +require_relative+ base. It is recommended these should be the same full + * path. + * + * +options+, which can be +true+, +false+ or a +Hash+, is used to + * modify the default behavior of the Ruby iseq compiler. + * + * For details regarding valid compile options see ::compile_option=. + * + * RubyVM::InstructionSequence.compile_parsey("a = 1 + 2") + * #=> @> + * + * path = "test.rb" + * RubyVM::InstructionSequence.compile_parsey(File.read(path), path, File.expand_path(path)) + * #=> @test.rb:1> + * + * file = File.open("test.rb") + * RubyVM::InstructionSequence.compile_parsey(file) + * #=> @:1> + * + * path = File.expand_path("test.rb") + * RubyVM::InstructionSequence.compile_parsey(File.read(path), path, path) + * #=> @/absolute/path/to/test.rb:1> + * + */ +static VALUE +iseqw_s_compile_parsey(int argc, VALUE *argv, VALUE self) +{ + return iseqw_s_compile_parser(argc, argv, self, false); +} + /* * call-seq: * InstructionSequence.compile_prism(source[, file[, path[, line[, options]]]]) -> iseq @@ -1589,19 +1631,19 @@ iseqw_s_compile(int argc, VALUE *argv, VALUE self) * * For details regarding valid compile options see ::compile_option=. * - * RubyVM::InstructionSequence.compile("a = 1 + 2") + * RubyVM::InstructionSequence.compile_prism("a = 1 + 2") * #=> @> * * path = "test.rb" - * RubyVM::InstructionSequence.compile(File.read(path), path, File.expand_path(path)) + * RubyVM::InstructionSequence.compile_prism(File.read(path), path, File.expand_path(path)) * #=> @test.rb:1> * * file = File.open("test.rb") - * RubyVM::InstructionSequence.compile(file) + * RubyVM::InstructionSequence.compile_prism(file) * #=> @:1> * * path = File.expand_path("test.rb") - * RubyVM::InstructionSequence.compile(File.read(path), path, path) + * RubyVM::InstructionSequence.compile_prism(File.read(path), path, path) * #=> @/absolute/path/to/test.rb:1> * */ @@ -4283,6 +4325,7 @@ Init_ISeq(void) (void)iseq_s_load; rb_define_singleton_method(rb_cISeq, "compile", iseqw_s_compile, -1); + rb_define_singleton_method(rb_cISeq, "compile_parsey", iseqw_s_compile_parsey, -1); rb_define_singleton_method(rb_cISeq, "compile_prism", iseqw_s_compile_prism, -1); rb_define_singleton_method(rb_cISeq, "compile_file_prism", iseqw_s_compile_file_prism, -1); rb_define_singleton_method(rb_cISeq, "new", iseqw_s_compile, -1); diff --git a/prism_compile.c b/prism_compile.c index a9c8aea4b4..5f5e0aa6fe 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6797,6 +6797,8 @@ pm_compile_array_node(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_list // Create the temporary array. for (; tmp_array_size; tmp_array_size--) rb_ary_push(tmp_array, pm_static_literal_value(iseq, elements->nodes[index++], scope_node)); + + index--; // about to be incremented by for loop OBJ_FREEZE(tmp_array); // Emit the optimized code. diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index afe39f9aef..92c1304e9b 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -768,6 +768,9 @@ module Prism assert_prism_eval("a = [1,2]; [0, *a, 3, 4, *5..6, 7, 8, *9..11]") assert_prism_eval("[[*1..2], 3, *4..5]") + elements = Array.new(64) { ":foo" } + assert_prism_eval("[#{elements.join(", ")}, bar: 1, baz: 2]") + # Test keyword splat inside of array assert_prism_eval("[**{x: 'hello'}]") @@ -2628,7 +2631,7 @@ end def compare_eval(source, raw:, location:) source = raw ? source : "class Prism::TestCompilePrism\n#{source}\nend" - ruby_eval = RubyVM::InstructionSequence.compile(source).eval + ruby_eval = RubyVM::InstructionSequence.compile_parsey(source).eval prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval if ruby_eval.is_a? Proc