Skip to content
Merged
20 changes: 10 additions & 10 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,12 @@ ary_make_shared_copy(VALUE ary)
return ary_make_partial(ary, rb_cArray, 0, RARRAY_LEN(ary));
}

static VALUE
ary_make_hidden_shared_copy(VALUE ary)
{
return ary_make_partial(ary, 0, 0, RARRAY_LEN(ary));
}

enum ary_take_pos_flags
{
ARY_TAKE_FIRST = 0,
Expand Down Expand Up @@ -7382,8 +7388,7 @@ rb_ary_permutation(int argc, VALUE *argv, VALUE ary)
volatile VALUE t0;
long *p = ALLOCV_N(long, t0, r+roomof(n, sizeof(long)));
char *used = (char*)(p + r);
VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */
RBASIC_CLEAR_CLASS(ary0);
VALUE ary0 = ary_make_hidden_shared_copy(ary); /* private defensive copy of ary */

MEMZERO(used, char, n); /* initialize array */

Expand Down Expand Up @@ -7488,11 +7493,10 @@ rb_ary_combination(VALUE ary, VALUE num)
}
}
else {
VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */
VALUE ary0 = ary_make_hidden_shared_copy(ary); /* private defensive copy of ary */
volatile VALUE t0;
long *stack = ALLOCV_N(long, t0, n+1);

RBASIC_CLEAR_CLASS(ary0);
combinate0(len, n, stack, ary0);
ALLOCV_END(t0);
RBASIC_SET_CLASS_RAW(ary0, rb_cArray);
Expand Down Expand Up @@ -7611,8 +7615,7 @@ rb_ary_repeated_permutation(VALUE ary, VALUE num)
else { /* this is the general case */
volatile VALUE t0;
long *p = ALLOCV_N(long, t0, r);
VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */
RBASIC_CLEAR_CLASS(ary0);
VALUE ary0 = ary_make_hidden_shared_copy(ary); /* private defensive copy of ary */

rpermute0(n, r, p, ary0); /* compute and yield repeated permutations */
ALLOCV_END(t0);
Expand Down Expand Up @@ -7719,8 +7722,7 @@ rb_ary_repeated_combination(VALUE ary, VALUE num)
else {
volatile VALUE t0;
long *p = ALLOCV_N(long, t0, n);
VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */
RBASIC_CLEAR_CLASS(ary0);
VALUE ary0 = ary_make_hidden_shared_copy(ary); /* private defensive copy of ary */

rcombinate0(len, n, p, n, ary0); /* compute and yield repeated combinations */
ALLOCV_END(t0);
Expand Down Expand Up @@ -7794,8 +7796,6 @@ rb_ary_product(int argc, VALUE *argv, VALUE ary)
long i,j;
long resultlen = 1;

RBASIC_CLEAR_CLASS(t0);

/* initialize the arrays of arrays */
ARY_SET_LEN(t0, n);
arrays[0] = ary;
Expand Down
3 changes: 3 additions & 0 deletions ext/json/generator/generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,9 @@ static VALUE cState_set_default_sort_keys_proc(VALUE self, VALUE proc)
if (!rb_obj_is_proc(proc)) {
rb_raise(rb_eTypeError, "sort_key_proc must be a Proc");
}
if (default_sort_keys_proc) {
rb_raise(rb_eArgError, "sort_key_proc can only be set once");
}
return default_sort_keys_proc = proc;
}

Expand Down
26 changes: 0 additions & 26 deletions ext/json/lib/json/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,6 @@ def parser=(parser) # :nodoc:
def generator=(generator) # :nodoc:
old, $VERBOSE = $VERBOSE, nil

unless generator::State.respond_to?(:default_sort_keys_proc_unchecked=, true)
generator::State.singleton_class.class_eval do
alias_method :default_sort_keys_proc_unchecked=, :default_sort_keys_proc=
private :default_sort_keys_proc_unchecked=

def default_sort_keys_proc=(proc)
unless ::Proc === proc
raise ::TypeError, "sort_key_proc must be a Proc"
end
if defined?(::Ractor) && !::Ractor.shareable?(proc) && !::Ractor.current.equal?(::Ractor.main)
raise ::Ractor::IsolationError,
"can not set a non-shareable Proc as the default sort_keys proc from a non-main Ractor"
end
self.default_sort_keys_proc_unchecked = proc
end
end
end

# The default proc used when the +sort_keys+ generation option is +true+.
# It returns a new hash with the entries sorted by their keys.
sort_keys_proc = ->(hash) { hash.sort.to_h }
if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda)
sort_keys_proc = Ractor.shareable_lambda(&sort_keys_proc)
end
generator::State.default_sort_keys_proc = sort_keys_proc

@generator = generator
if generator.const_defined?(:GeneratorMethods)
generator_methods = generator::GeneratorMethods
Expand Down
15 changes: 12 additions & 3 deletions ext/json/lib/json/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,22 @@ def parse
Ext::Parser::Config = Ext::ParserConfig
JSON.parser = Ext::Parser

if RUBY_ENGINE == 'truffleruby'
generator = if RUBY_ENGINE == 'truffleruby'
require 'json/truffle_ruby/generator'
JSON.generator = JSON::TruffleRuby::Generator
JSON::TruffleRuby::Generator
else
require 'json/ext/generator'
JSON.generator = Generator
Generator
end

# The default proc used when the +sort_keys+ generation option is +true+.
# It returns a new hash with the entries sorted by their keys.
sort_keys_proc = ->(hash) { hash.sort.to_h }
if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda)
sort_keys_proc = Ractor.shareable_lambda(&sort_keys_proc)
end
generator::State.default_sort_keys_proc = sort_keys_proc
JSON.generator = generator
end

if defined?(ResumableParser) # Not yet available on JRuby
Expand Down
36 changes: 29 additions & 7 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -17628,22 +17628,44 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm
}
}

/**
* Add an error for each variable captured by the given pattern. Only descend
* into nodes that make up the pattern itself: a lambda, a string interpolation,
* or a pinned expression can contain local variable targets of its own that
* are not captures of this pattern. A nested alternation is not visited
* either: its operands were already checked when it was parsed.
*/
static bool
parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
pm_parser_t *parser = (pm_parser_t *) data;

switch (PM_NODE_TYPE(node)) {
case PM_LOCAL_VARIABLE_TARGET_NODE: {
pm_parser_t *parser = (pm_parser_t *) data;
pm_parser_err(parser, PM_NODE_START(node), PM_NODE_LENGTH(node), PM_ERR_PATTERN_CAPTURE_IN_ALTERNATIVE);
case PM_LOCAL_VARIABLE_TARGET_NODE:
// Underscore-prefixed names are not captures, see
// parse_pattern_capture.
if (peek_at(parser, parser->start + PM_NODE_START(node)) != '_') {
pm_parser_err(parser, PM_NODE_START(node), PM_NODE_LENGTH(node), PM_ERR_PATTERN_CAPTURE_IN_ALTERNATIVE);
}
return false;
}
default:
case PM_ARRAY_PATTERN_NODE:
case PM_ASSOC_NODE:
case PM_ASSOC_SPLAT_NODE:
case PM_CAPTURE_PATTERN_NODE:
case PM_FIND_PATTERN_NODE:
case PM_HASH_PATTERN_NODE:
case PM_IMPLICIT_NODE:
case PM_PARENTHESES_NODE:
case PM_SPLAT_NODE:
return true;
default:
return false;
}
}

/**
* When we get here, we know that we already have a syntax error, because we
* know we have captured a variable and that we are in an alternation.
* Called when we are in an alternation and a variable has been captured
* somewhere in the pattern. That capture may be outside of the given node, so
* this only adds errors for the captures that are actually inside of it.
*/
static void
parse_pattern_alternation_error(pm_parser_t *parser, const pm_node_t *node) {
Expand Down
6 changes: 6 additions & 0 deletions ractor_sync.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ ractor_port_alloc(VALUE klass)
static VALUE
ractor_port_init(VALUE rpv, rb_ractor_t *r)
{
// Child threads can still run ensure blocks and report exceptions after
// ractor_notify_exit has freed the ports.
if (!r->sync.ports) {
rb_raise(rb_eRactorClosedError, "The ractor has terminated");
}

struct ractor_port *rp = RACTOR_PORT_PTR(rpv);

rp->r = r;
Expand Down
12 changes: 0 additions & 12 deletions test/json/json_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,18 +248,6 @@ def test_generate_sort_keys_with_proc
assert_instance_of Proc, state.sort_keys
end

def test_default_sort_keys_proc_setter_survives_generator_reassignment
omit "fork not supported" unless Process.respond_to?(:fork)
pid = fork do
JSON.generator = JSON::Ext::Generator
JSON.generator = JSON::Ext::Generator
JSON::State.default_sort_keys_proc = ->(hash) { hash.sort.to_h }
exit!(JSON.generate({b: 1, a: 2}, sort_keys: true) == '{"a":2,"b":1}' ? 0 : 1)
end
_, status = Process.wait2(pid)
assert_predicate status, :success?
end

def test_generate_custom
state = State.new(space_before: " ", space: " ", indent: "<i>", object_nl: "\n", array_nl: "<a_nl>")
json = generate({1=>{2=>3,4=>[5,6]}}, state)
Expand Down
29 changes: 0 additions & 29 deletions test/json/ractor_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,4 @@ def test_coder_proc
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end if Ractor.respond_to?(:shareable_proc)

def test_default_sort_keys_proc_ractor_safety
pid = fork do
Warning[:experimental] = false
results = Ractor.new do
outcomes = []

begin
JSON::State.default_sort_keys_proc = ->(hash) { hash.sort.to_h }
outcomes << :accepted
rescue Ractor::IsolationError
outcomes << :rejected
end

# A shareable Proc is allowed from any Ractor.
JSON::State.default_sort_keys_proc = Ractor.shareable_lambda { |hash| hash.sort.reverse.to_h }
outcomes << JSON.generate({b: 1, a: 2}, sort_keys: true)
outcomes
end.value

# The main Ractor owns the slot, so it may install any Proc.
JSON::State.default_sort_keys_proc = ->(hash) { hash.sort.to_h }
main_result = JSON.generate({b: 1, a: 2}, sort_keys: true)

exit(results == [:rejected, '{"b":1,"a":2}'] && main_result == '{"a":2,"b":1}' ? 0 : 1)
end
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end if Ractor.respond_to?(:shareable_lambda)
end if defined?(Ractor) && Process.respond_to?(:fork)
3 changes: 3 additions & 0 deletions test/prism/errors/pattern-capture-in-alt-find.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 => [x, [*, y, *] | 1]
^ variable capture in alternative pattern

3 changes: 3 additions & 0 deletions test/prism/errors/pattern-capture-in-alt-implicit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 => [x, { a: } | 1]
^ variable capture in alternative pattern

3 changes: 3 additions & 0 deletions test/prism/errors/pattern-capture-in-alt-inner.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 => [x, (1 | y) | 2]
^ variable capture in alternative pattern

3 changes: 3 additions & 0 deletions test/prism/errors/pattern-capture-in-alt-kwsplat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 => [x, { **y } | 1]
^ variable capture in alternative pattern

3 changes: 3 additions & 0 deletions test/prism/errors/pattern-capture-in-alt-nested.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 => 1 | [2 | 3, y]
^ variable capture in alternative pattern

3 changes: 3 additions & 0 deletions test/prism/errors/pattern-capture-in-alt-splat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 => [x, [*y] | 1]
^ variable capture in alternative pattern

31 changes: 31 additions & 0 deletions test/prism/fixtures/pattern_alternation_non_captures.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
case foo
in [bar, 1 | _baz]
end

case foo
in [bar, _baz | 1]
end

case foo
in [bar, "#{case 1; in baz; end}" | 2]
end

case foo
in [bar, -> { case 1; in baz; end } | 2]
end

case foo
in [bar, ^(case 1; in baz; end) | 2]
end

case foo
in [bar, [*_baz] | 1]
end

case foo
in [bar, { **_baz } | 1]
end

case foo
in [bar, { _baz: } | 1]
end
2 changes: 2 additions & 0 deletions test/prism/fixtures_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class FixturesTest < TestCase
except << "4.1/void_value.txt"
# https://bugs.ruby-lang.org/issues/19107
except << "4.1/trailing_comma_after_method_arguments.txt"
# https://bugs.ruby-lang.org/issues/22310
except << "pattern_alternation_non_captures.txt"

Fixture.each_for_current_ruby(except: except) do |fixture|
define_method(fixture.test_name) { assert_valid_syntax(fixture.read) }
Expand Down
3 changes: 3 additions & 0 deletions test/prism/locals_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class LocalsTest < TestCase

# https://bugs.ruby-lang.org/issues/19107
"4.1/trailing_comma_after_method_arguments.txt",

# https://bugs.ruby-lang.org/issues/22310
"pattern_alternation_non_captures.txt",
]

Fixture.each_for_current_ruby(except: except) do |fixture|
Expand Down
2 changes: 2 additions & 0 deletions test/prism/ruby/ripper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class RipperTest < TestCase
incorrect += [
# https://bugs.ruby-lang.org/issues/21945
"and_or_with_suffix.txt",
# https://bugs.ruby-lang.org/issues/22310
"pattern_alternation_non_captures.txt",
]
end

Expand Down
23 changes: 23 additions & 0 deletions test/ruby/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,29 @@ def test_ractor_with_live_threads_terminates_without_waiting
end


def test_new_port_during_teardown
assert_in_out_err(%w[-W0], <<~'RUBY', %w[closed done], [], success: true)
worker = Ractor.new do
ready = Thread::Queue.new
Thread.new do
begin
ready << true
sleep
ensure
begin
Ractor::Port.new
rescue Ractor::ClosedError
puts "closed"
end
end
end
ready.pop
:done
end
puts worker.value
RUBY
end

def test_class_instance_variables
assert_ractor(<<~'RUBY')
# Once we're in multi-ractor mode, the codepaths
Expand Down
2 changes: 1 addition & 1 deletion tool/ruby_vm/views/_zjit_instruction.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
INSN_ENTRY(<%= insn.name %>)
{
START_OF_ORIGINAL_INSN(<%= insn.name %>);
rb_zjit_profile_insn(BIN(<%= insn.jump_destination %>), ec);
%# opt_case_dispatch jumps straight to the matching `when` body, skipping the
%# `===` chain it guards. While profiling, pop the key and fall through to the
%# `===` chain instead so that its instructions and the dispatched blocks get
Expand All @@ -16,7 +17,6 @@ INSN_ENTRY(<%= insn.name %>)
% end
INC_SP(<%= insn.call_attribute('sp_inc') %>);
% else
rb_zjit_profile_insn(BIN(<%= insn.jump_destination %>), ec);
DISPATCH_ORIGINAL_INSN(<%= insn.jump_destination %>);
% end
END_INSN(<%= insn.name %>);
Expand Down
3 changes: 2 additions & 1 deletion win32/lastrev.bat
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@echo off
@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1

if "%1" == "" (set gitdir=.) else (set gitdir=%1)
if "%1" == "" (set gitdir=.) else (set "gitdir=%1")
set TZ=UTC
::- shorten %H for the stabilities, the length of %h may vary
for /f "usebackq tokens=1-3" %%I in (
`git -C "%gitdir%" log -1 --no-show-signature "--date=format-local:%%F %%T" "--format=%%H %%cd" HEAD`
) do (
Expand Down
Loading