diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml
index 5e54d39e9fa6b9..8c0ca54e0b100b 100644
--- a/.github/workflows/compilers.yml
+++ b/.github/workflows/compilers.yml
@@ -244,7 +244,7 @@ jobs:
- { uses: './.github/actions/compilers', name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' }, timeout-minutes: 5 }
- { uses: './.github/actions/compilers', name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' }, timeout-minutes: 5 }
- { uses: './.github/actions/compilers', name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' }, timeout-minutes: 5 }
- - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit' }, timeout-minutes: 5 }
+ - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 }
compileX:
name: 'omnibus compilations, #10'
diff --git a/NEWS.md b/NEWS.md
index 784b92b717d1c5..e7d1da919e631d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -234,30 +234,32 @@ releases.
The following default gem is added.
-* win32-registry 0.1.1
+* win32-registry 0.1.2
The following default gems are updated.
* RubyGems 4.0.1
* bundler 4.0.1
-* date 3.5.0
+* date 3.5.1
* digest 3.2.1
* english 0.8.1
* erb 6.0.0
* etc 1.4.6
* fcntl 1.3.0
* fileutils 1.8.0
+* forwardable 1.4.0
* io-console 0.8.1
* io-nonblock 0.3.2
* io-wait 0.4.0.dev
+* ipaddr 1.2.8
* json 2.17.1
* net-http 0.8.0
* openssl 4.0.0.pre
-* optparse 0.8.0
+* optparse 0.8.1
* pp 0.6.3
* prism 1.6.0
-* psych 5.2.6
-* resolv 0.6.3
+* psych 5.3.0
+* resolv 0.7.0
* stringio 3.1.9.dev
* strscan 3.1.6.dev
* timeout 0.5.0
diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb
index 81dc2d6b8d04be..13c4652d3760a8 100644
--- a/bootstraptest/test_ractor.rb
+++ b/bootstraptest/test_ractor.rb
@@ -1494,6 +1494,9 @@ class C
unless a[i].equal?(b[i])
raise [a[i], b[i]].inspect
end
+ unless a[i] == i.to_s
+ raise [i, a[i], b[i]].inspect
+ end
end
:ok
}
diff --git a/box.c b/box.c
index 0ce8d0aee02f0f..7907e0ff632a1e 100644
--- a/box.c
+++ b/box.c
@@ -760,7 +760,7 @@ static int
cleanup_local_extension_i(VALUE key, VALUE value, VALUE arg)
{
#if defined(_WIN32)
- HMODULE h = (HMODULE)NUM2SVALUE(value);
+ HMODULE h = (HMODULE)NUM2PTR(value);
WCHAR module_path[MAXPATHLEN];
DWORD len = GetModuleFileNameW(h, module_path, numberof(module_path));
diff --git a/compile.c b/compile.c
index a5d821eb810cf1..bcf22243cfc7af 100644
--- a/compile.c
+++ b/compile.c
@@ -610,8 +610,6 @@ branch_coverage_valid_p(rb_iseq_t *iseq, int first_line)
return 1;
}
-#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
-
static VALUE
setup_branch(const rb_code_location_t *loc, const char *type, VALUE structure, VALUE key)
{
diff --git a/concurrent_set.c b/concurrent_set.c
index 3aa61507aaa7d2..234b6408b6b938 100644
--- a/concurrent_set.c
+++ b/concurrent_set.c
@@ -4,6 +4,9 @@
#include "ruby/atomic.h"
#include "vm_sync.h"
+#define CONCURRENT_SET_CONTINUATION_BIT ((VALUE)1 << (sizeof(VALUE) * CHAR_BIT - 1))
+#define CONCURRENT_SET_HASH_MASK (~CONCURRENT_SET_CONTINUATION_BIT)
+
enum concurrent_set_special_values {
CONCURRENT_SET_EMPTY,
CONCURRENT_SET_DELETED,
@@ -24,6 +27,36 @@ struct concurrent_set {
struct concurrent_set_entry *entries;
};
+static void
+concurrent_set_mark_continuation(struct concurrent_set_entry *entry, VALUE curr_hash_and_flags)
+{
+ if (curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT) return;
+
+ RUBY_ASSERT((curr_hash_and_flags & CONCURRENT_SET_HASH_MASK) != 0);
+
+ VALUE new_hash = curr_hash_and_flags | CONCURRENT_SET_CONTINUATION_BIT;
+ VALUE prev_hash = rbimpl_atomic_value_cas(&entry->hash, curr_hash_and_flags, new_hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
+
+ // At the moment we only expect to be racing concurrently against another
+ // thread also setting the continuation bit.
+ // In the future if deletion is concurrent this will need adjusting
+ RUBY_ASSERT(prev_hash == curr_hash_and_flags || prev_hash == new_hash);
+ (void)prev_hash;
+}
+
+static VALUE
+concurrent_set_hash(const struct concurrent_set *set, VALUE key)
+{
+ VALUE hash = set->funcs->hash(key);
+ hash &= CONCURRENT_SET_HASH_MASK;
+ if (hash == 0) {
+ hash ^= CONCURRENT_SET_HASH_MASK;
+ }
+ RUBY_ASSERT(hash != 0);
+ RUBY_ASSERT(!(hash & CONCURRENT_SET_CONTINUATION_BIT));
+ return hash;
+}
+
static void
concurrent_set_free(void *ptr)
{
@@ -134,20 +167,16 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr)
struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj);
for (int i = 0; i < old_capacity; i++) {
- struct concurrent_set_entry *entry = &old_entries[i];
- VALUE key = rbimpl_atomic_value_exchange(&entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE);
+ struct concurrent_set_entry *old_entry = &old_entries[i];
+ VALUE key = rbimpl_atomic_value_exchange(&old_entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE);
RUBY_ASSERT(key != CONCURRENT_SET_MOVED);
if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT) continue;
if (!RB_SPECIAL_CONST_P(key) && rb_objspace_garbage_object_p(key)) continue;
- VALUE hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED);
- if (hash == 0) {
- // Either in-progress insert or extremely unlikely 0 hash.
- // Re-calculate the hash.
- hash = old_set->funcs->hash(key);
- }
- RUBY_ASSERT(hash == old_set->funcs->hash(key));
+ VALUE hash = rbimpl_atomic_value_load(&old_entry->hash, RBIMPL_ATOMIC_RELAXED) & CONCURRENT_SET_HASH_MASK;
+ RUBY_ASSERT(hash != 0);
+ RUBY_ASSERT(hash == concurrent_set_hash(old_set, key));
// Insert key into new_set.
struct concurrent_set_probe probe;
@@ -156,20 +185,19 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr)
while (true) {
struct concurrent_set_entry *entry = &new_set->entries[idx];
- if (entry->key == CONCURRENT_SET_EMPTY) {
- new_set->size++;
+ if (entry->hash == CONCURRENT_SET_EMPTY) {
+ RUBY_ASSERT(entry->key == CONCURRENT_SET_EMPTY);
+ new_set->size++;
RUBY_ASSERT(new_set->size <= new_set->capacity / 2);
- RUBY_ASSERT(entry->hash == 0);
entry->key = key;
entry->hash = hash;
break;
}
- else {
- RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT);
- }
+ RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT);
+ entry->hash |= CONCURRENT_SET_CONTINUATION_BIT;
idx = concurrent_set_probe_next(&probe);
}
}
@@ -194,29 +222,48 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key)
VALUE set_obj;
VALUE hash = 0;
+ struct concurrent_set *set;
+ struct concurrent_set_probe probe;
+ int idx;
retry:
set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
RUBY_ASSERT(set_obj);
- struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj);
+ set = RTYPEDDATA_GET_DATA(set_obj);
if (hash == 0) {
// We don't need to recompute the hash on every retry because it should
// never change.
- hash = set->funcs->hash(key);
+ hash = concurrent_set_hash(set, key);
}
- RUBY_ASSERT(hash == set->funcs->hash(key));
+ RUBY_ASSERT(hash == concurrent_set_hash(set, key));
- struct concurrent_set_probe probe;
- int idx = concurrent_set_probe_start(&probe, set, hash);
+ idx = concurrent_set_probe_start(&probe, set, hash);
while (true) {
struct concurrent_set_entry *entry = &set->entries[idx];
+ VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE);
+ VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK;
+ bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT;
+
+ if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) {
+ return 0;
+ }
+
+ if (curr_hash != hash) {
+ if (!continuation) {
+ return 0;
+ }
+ idx = concurrent_set_probe_next(&probe);
+ continue;
+ }
+
VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE);
switch (curr_key) {
case CONCURRENT_SET_EMPTY:
- return 0;
+ // In-progress insert: hash written but key not yet
+ break;
case CONCURRENT_SET_DELETED:
break;
case CONCURRENT_SET_MOVED:
@@ -225,13 +272,9 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key)
goto retry;
default: {
- VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED);
- if (curr_hash != 0 && curr_hash != hash) break;
-
if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && rb_objspace_garbage_object_p(curr_key))) {
// This is a weakref set, so after marking but before sweeping is complete we may find a matching garbage object.
- // Skip it and mark it as deleted.
- rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
+ // Skip it and let the GC pass clean it up
break;
}
@@ -241,6 +284,10 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key)
return curr_key;
}
+ if (!continuation) {
+ return 0;
+ }
+
break;
}
}
@@ -254,38 +301,65 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data)
{
RUBY_ASSERT(key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT);
- bool inserting = false;
- VALUE set_obj;
- VALUE hash = 0;
+ // First attempt to find
+ {
+ VALUE result = rb_concurrent_set_find(set_obj_ptr, key);
+ if (result) return result;
+ }
- retry:
- set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
+ // First time we need to call create, and store the hash
+ VALUE set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
RUBY_ASSERT(set_obj);
- struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj);
- if (hash == 0) {
- // We don't need to recompute the hash on every retry because it should
- // never change.
- hash = set->funcs->hash(key);
- }
- RUBY_ASSERT(hash == set->funcs->hash(key));
+ struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj);
+ key = set->funcs->create(key, data);
+ VALUE hash = concurrent_set_hash(set, key);
struct concurrent_set_probe probe;
- int idx = concurrent_set_probe_start(&probe, set, hash);
+ int idx;
+
+ goto start_search;
+
+retry:
+ // On retries we only need to load the hash object
+ set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
+ RUBY_ASSERT(set_obj);
+ set = RTYPEDDATA_GET_DATA(set_obj);
+
+ RUBY_ASSERT(hash == concurrent_set_hash(set, key));
+
+start_search:
+ idx = concurrent_set_probe_start(&probe, set, hash);
while (true) {
struct concurrent_set_entry *entry = &set->entries[idx];
+ VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE);
+ VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK;
+ bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT;
+
+ if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) {
+ // Reserve this slot for our hash value
+ curr_hash_and_flags = rbimpl_atomic_value_cas(&entry->hash, CONCURRENT_SET_EMPTY, hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
+ if (curr_hash_and_flags != CONCURRENT_SET_EMPTY) {
+ // Lost race, retry same slot to check winner's hash
+ continue;
+ }
+
+ // CAS succeeded, so these are the values stored
+ curr_hash_and_flags = hash;
+ curr_hash = hash;
+
+ // Fall through to try to claim key
+ }
+
+ if (curr_hash != hash) {
+ goto probe_next;
+ }
+
VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE);
switch (curr_key) {
case CONCURRENT_SET_EMPTY: {
- // Not in set
- if (!inserting) {
- key = set->funcs->create(key, data);
- RUBY_ASSERT(hash == set->funcs->hash(key));
- inserting = true;
- }
-
rb_atomic_t prev_size = rbimpl_atomic_fetch_add(&set->size, 1, RBIMPL_ATOMIC_RELAXED);
// Load_factor reached at 75% full. ex: prev_size: 32, capacity: 64, load_factor: 50%.
@@ -293,14 +367,12 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data)
if (UNLIKELY(load_factor_reached)) {
concurrent_set_try_resize(set_obj, set_obj_ptr);
-
goto retry;
}
- curr_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
- if (curr_key == CONCURRENT_SET_EMPTY) {
- rbimpl_atomic_value_store(&entry->hash, hash, RBIMPL_ATOMIC_RELAXED);
-
+ VALUE prev_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
+ if (prev_key == CONCURRENT_SET_EMPTY) {
+ RUBY_ASSERT(rb_concurrent_set_find(set_obj_ptr, key) == key);
RB_GC_GUARD(set_obj);
return key;
}
@@ -317,41 +389,58 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data)
case CONCURRENT_SET_MOVED:
// Wait
RB_VM_LOCKING();
-
goto retry;
- default: {
- VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED);
- if (curr_hash != 0 && curr_hash != hash) break;
-
+ default:
+ // We're never GC during our search
+ // If the continuation bit wasn't set at the start of our search,
+ // any concurrent find with the same hash value would also look at
+ // this location and try to swap curr_key
if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && rb_objspace_garbage_object_p(curr_key))) {
- // This is a weakref set, so after marking but before sweeping is complete we may find a matching garbage object.
- // Skip it and mark it as deleted.
- rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
- break;
+ if (continuation) {
+ goto probe_next;
+ }
+ rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_EMPTY, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
+ continue;
}
if (set->funcs->cmp(key, curr_key)) {
- // We've found a match.
+ // We've found a live match.
RB_GC_GUARD(set_obj);
- if (inserting) {
- // We created key using set->funcs->create, but we didn't end
- // up inserting it into the set. Free it here to prevent memory
- // leaks.
- if (set->funcs->free) set->funcs->free(key);
- }
+ // We created key using set->funcs->create, but we didn't end
+ // up inserting it into the set. Free it here to prevent memory
+ // leaks.
+ if (set->funcs->free) set->funcs->free(key);
return curr_key;
}
-
break;
- }
}
+ probe_next:
+ RUBY_ASSERT(curr_hash_and_flags != CONCURRENT_SET_EMPTY);
+ concurrent_set_mark_continuation(entry, curr_hash_and_flags);
idx = concurrent_set_probe_next(&probe);
}
}
+static void
+concurrent_set_delete_entry_locked(struct concurrent_set *set, struct concurrent_set_entry *entry)
+{
+ ASSERT_vm_locking_with_barrier();
+
+ if (entry->hash & CONCURRENT_SET_CONTINUATION_BIT) {
+ entry->hash = CONCURRENT_SET_CONTINUATION_BIT;
+ entry->key = CONCURRENT_SET_DELETED;
+ set->deleted_entries++;
+ }
+ else {
+ entry->hash = CONCURRENT_SET_EMPTY;
+ entry->key = CONCURRENT_SET_EMPTY;
+ set->size--;
+ }
+}
+
VALUE
rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key)
{
@@ -359,7 +448,7 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key)
struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj);
- VALUE hash = set->funcs->hash(key);
+ VALUE hash = concurrent_set_hash(set, key);
struct concurrent_set_probe probe;
int idx = concurrent_set_probe_start(&probe, set, hash);
@@ -379,8 +468,8 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key)
break;
default:
if (key == curr_key) {
- entry->key = CONCURRENT_SET_DELETED;
- set->deleted_entries++;
+ RUBY_ASSERT((entry->hash & CONCURRENT_SET_HASH_MASK) == hash);
+ concurrent_set_delete_entry_locked(set, entry);
return curr_key;
}
break;
@@ -399,7 +488,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key
for (unsigned int i = 0; i < set->capacity; i++) {
struct concurrent_set_entry *entry = &set->entries[i];
- VALUE key = set->entries[i].key;
+ VALUE key = entry->key;
switch (key) {
case CONCURRENT_SET_EMPTY:
@@ -414,8 +503,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key
case ST_STOP:
return;
case ST_DELETE:
- set->entries[i].key = CONCURRENT_SET_DELETED;
- set->deleted_entries++;
+ concurrent_set_delete_entry_locked(set, entry);
break;
}
break;
diff --git a/debug.c b/debug.c
index b92faa8f369398..4daee2bd1cbd0d 100644
--- a/debug.c
+++ b/debug.c
@@ -708,4 +708,22 @@ ruby_debug_log_dump(const char *fname, unsigned int n)
fclose(fp);
}
}
+
+#else
+
+#undef ruby_debug_log
+void
+ruby_debug_log(const char *file, int line, const char *func_name, const char *fmt, ...)
+{
+ va_list args;
+
+ fprintf(stderr, "[%s:%d] %s: ", file, line, func_name);
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "\n");
+}
+
#endif // #if USE_RUBY_DEBUG_LOG
diff --git a/doc/stringio/each_line.md b/doc/stringio/each_line.md
new file mode 100644
index 00000000000000..e29640a12a9203
--- /dev/null
+++ b/doc/stringio/each_line.md
@@ -0,0 +1,189 @@
+With a block given calls the block with each remaining line (see "Position" below) in the stream;
+returns `self`.
+
+Leaves stream position at end-of-stream.
+
+**No Arguments**
+
+With no arguments given,
+reads lines using the default record separator
+(global variable `$/`, whose initial value is `"\n"`).
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line {|line| p line }
+strio.eof? # => true
+```
+
+Output:
+
+```
+"First line\n"
+"Second line\n"
+"\n"
+"Fourth line\n"
+"Fifth line\n"
+```
+
+**Argument `sep`**
+
+With only string argument `sep` given,
+reads lines using that string as the record separator:
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line(' ') {|line| p line }
+```
+
+Output:
+
+```
+"First "
+"line\nSecond "
+"line\n\nFourth "
+"line\nFifth "
+"line\n"
+```
+
+**Argument `limit`**
+
+With only integer argument `limit` given,
+reads lines using the default record separator;
+also limits the size (in characters) of each line to the given limit:
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line(10) {|line| p line }
+```
+
+Output:
+
+```
+"First line"
+"\n"
+"Second lin"
+"e\n"
+"\n"
+"Fourth lin"
+"e\n"
+"Fifth line"
+"\n"
+```
+
+**Arguments `sep` and `limit`**
+
+With arguments `sep` and `limit` both given,
+honors both:
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line(' ', 10) {|line| p line }
+```
+
+Output:
+
+```
+"First "
+"line\nSecon"
+"d "
+"line\n\nFour"
+"th "
+"line\nFifth"
+" "
+"line\n"
+```
+
+**Position**
+
+As stated above, method `each` _remaining_ line in the stream.
+
+In the examples above each `strio` object starts with its position at beginning-of-stream;
+but in other cases the position may be anywhere (see StringIO#pos):
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.pos = 30 # Set stream position to character 30.
+strio.each_line {|line| p line }
+```
+
+Output:
+
+```
+" line\n"
+"Fifth line\n"
+```
+
+In all the examples above, the stream position is at the beginning of a character;
+in other cases, that need not be so:
+
+```ruby
+s = 'こんにちは' # Five 3-byte characters.
+strio = StringIO.new(s)
+strio.pos = 3 # At beginning of second character.
+strio.each_line {|line| p line }
+strio.pos = 4 # At second byte of second character.
+strio.each_line {|line| p line }
+strio.pos = 5 # At third byte of second character.
+strio.each_line {|line| p line }
+```
+
+Output:
+
+```
+"んにちは"
+"\x82\x93にちは"
+"\x93にちは"
+```
+
+**Special Record Separators**
+
+Like some methods in class `IO`, StringIO.each honors two special record separators;
+see {Special Line Separators}[https://docs.ruby-lang.org/en/master/IO.html#class-IO-label-Special+Line+Separator+Values].
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line('') {|line| p line } # Read as paragraphs (separated by blank lines).
+```
+
+Output:
+
+```
+"First line\nSecond line\n\n"
+"Fourth line\nFifth line\n"
+```
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line(nil) {|line| p line } # "Slurp"; read it all.
+```
+
+Output:
+
+```
+"First line\nSecond line\n\nFourth line\nFifth line\n"
+```
+
+**Keyword Argument `chomp`**
+
+With keyword argument `chomp` given as `true` (the default is `false`),
+removes trailing newline (if any) from each line:
+
+```ruby
+strio = StringIO.new(TEXT)
+strio.each_line(chomp: true) {|line| p line }
+```
+
+Output:
+
+```
+"First line"
+"Second line"
+""
+"Fourth line"
+"Fifth line"
+```
+
+With no block given, returns a new {Enumerator}[https://docs.ruby-lang.org/en/master/Enumerator.html].
+
+
+Related: StringIO.each_byte, StringIO.each_char, StringIO.each_codepoint.
diff --git a/doc/stringio/getbyte.rdoc b/doc/stringio/getbyte.rdoc
index 48c334b5252a58..5e524941bca4ae 100644
--- a/doc/stringio/getbyte.rdoc
+++ b/doc/stringio/getbyte.rdoc
@@ -14,16 +14,18 @@ Returns +nil+ if at end-of-stream:
Returns a byte, not a character:
- s = 'тест'
- s.bytes # => [209, 130, 208, 181, 209, 129, 209, 130]
+ s = 'Привет'
+ s.bytes
+ # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
strio = StringIO.new(s)
- strio.getbyte # => 209
- strio.getbyte # => 130
+ strio.getbyte # => 208
+ strio.getbyte # => 159
s = 'こんにちは'
- s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175]
+ s.bytes
+ # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175]
strio = StringIO.new(s)
strio.getbyte # => 227
strio.getbyte # => 129
-Related: StringIO.getc.
+Related: #each_byte, #ungetbyte, #getc.
diff --git a/doc/stringio/getc.rdoc b/doc/stringio/getc.rdoc
index c021789c911b8d..b2ab46843c8466 100644
--- a/doc/stringio/getc.rdoc
+++ b/doc/stringio/getc.rdoc
@@ -12,9 +12,9 @@ Returns +nil+ if at end-of-stream:
Returns characters, not bytes:
- strio = StringIO.new('тест')
- strio.getc # => "т"
- strio.getc # => "е"
+ strio = StringIO.new('Привет')
+ strio.getc # => "П"
+ strio.getc # => "р"
strio = StringIO.new('こんにちは')
strio.getc # => "こ"
@@ -31,4 +31,4 @@ in other cases that need not be true:
strio.pos = 5 # => 5 # At third byte of second character; returns byte.
strio.getc # => "\x93"
-Related: StringIO.getbyte.
+Related: #getbyte, #putc, #ungetc.
diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc
index 892c3feb53a9bf..bbefeb008ae245 100644
--- a/doc/stringio/gets.rdoc
+++ b/doc/stringio/gets.rdoc
@@ -19,10 +19,10 @@ With no arguments given, reads a line using the default record separator
strio.eof? # => true
strio.gets # => nil
- strio = StringIO.new('тест') # Four 2-byte characters.
+ strio = StringIO.new('Привет') # Six 2-byte characters
strio.pos # => 0
- strio.gets # => "тест"
- strio.pos # => 8
+ strio.gets # => "Привет"
+ strio.pos # => 12
Argument +sep+
@@ -67,11 +67,11 @@ but in other cases the position may be anywhere:
The position need not be at a character boundary:
- strio = StringIO.new('тест') # Four 2-byte characters.
- strio.pos = 2 # At beginning of second character.
- strio.gets # => "ест"
- strio.pos = 3 # In middle of second character.
- strio.gets # => "\xB5ст"
+ strio = StringIO.new('Привет') # Six 2-byte characters.
+ strio.pos = 2 # At beginning of second character.
+ strio.gets # => "ривет"
+ strio.pos = 3 # In middle of second character.
+ strio.gets # => "\x80ивет"
Special Record Separators
@@ -95,4 +95,5 @@ removes the trailing newline (if any) from the returned line:
strio.gets # => "First line\n"
strio.gets(chomp: true) # => "Second line"
-Related: StringIO.each_line.
+Related: #each_line, #readlines,
+{Kernel#puts}[rdoc-ref:Kernel#puts].
diff --git a/doc/stringio/size.rdoc b/doc/stringio/size.rdoc
new file mode 100644
index 00000000000000..9323adf8c3783a
--- /dev/null
+++ b/doc/stringio/size.rdoc
@@ -0,0 +1,5 @@
+Returns the number of bytes in the string in +self+:
+
+ StringIO.new('hello').size # => 5 # Five 1-byte characters.
+ StringIO.new('тест').size # => 8 # Four 2-byte characters.
+ StringIO.new('こんにちは').size # => 15 # Five 3-byte characters.
diff --git a/doc/stringio/stringio.md b/doc/stringio/stringio.md
index aebfa6d6f1f4c2..8931d1c30c7d79 100644
--- a/doc/stringio/stringio.md
+++ b/doc/stringio/stringio.md
@@ -324,7 +324,7 @@ a binary stream may not be changed to text.
### Encodings
-A stream has an encoding; see the [encodings document][encodings document].
+A stream has an encoding; see [Encodings][encodings document].
The initial encoding for a new or re-opened stream depends on its [data mode][data mode]:
@@ -683,7 +683,7 @@ Reading:
- #each_codepoint: reads each remaining codepoint, passing it to the block.
[bom]: https://en.wikipedia.org/wiki/Byte_order_mark
-[encodings document]: https://docs.ruby-lang.org/en/master/encodings_rdoc.html
+[encodings document]: https://docs.ruby-lang.org/en/master/language/encodings_rdoc.html
[io class]: https://docs.ruby-lang.org/en/master/IO.html
[kernel#puts]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-puts
[kernel#readline]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-readline
diff --git a/ext/-test-/fatal/invalid.c b/ext/-test-/fatal/invalid.c
index 393465416a0f6c..6fd970b181191c 100644
--- a/ext/-test-/fatal/invalid.c
+++ b/ext/-test-/fatal/invalid.c
@@ -1,11 +1,5 @@
#include
-#if SIZEOF_LONG == SIZEOF_VOIDP
-# define NUM2PTR(x) NUM2ULONG(x)
-#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
-# define NUM2PTR(x) NUM2ULL(x)
-#endif
-
static VALUE
invalid_call(VALUE obj, VALUE address)
{
diff --git a/ext/date/lib/date.rb b/ext/date/lib/date.rb
index b33f6e65f466b0..0cb763017f22c0 100644
--- a/ext/date/lib/date.rb
+++ b/ext/date/lib/date.rb
@@ -4,7 +4,7 @@
require 'date_core'
class Date
- VERSION = "3.5.0" # :nodoc:
+ VERSION = "3.5.1" # :nodoc:
# call-seq:
# infinite? -> false
diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c
index c84c7ed660a06d..45de8d1ff62f1d 100644
--- a/ext/json/parser/parser.c
+++ b/ext/json/parser/parser.c
@@ -752,6 +752,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
break;
default:
if ((unsigned char)*pe < 0x20) {
+ if (*pe == '\n') {
+ raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1);
+ }
raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1);
}
raise_parse_error_at("invalid escape character in string: %s", state, pe - 1);
diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c
index 819c6e76996f61..aeb96d7701242d 100644
--- a/ext/monitor/monitor.c
+++ b/ext/monitor/monitor.c
@@ -4,28 +4,35 @@
struct rb_monitor {
long count;
- const VALUE owner;
- const VALUE mutex;
+ VALUE owner;
+ VALUE mutex;
};
static void
monitor_mark(void *ptr)
{
struct rb_monitor *mc = ptr;
- rb_gc_mark(mc->owner);
- rb_gc_mark(mc->mutex);
+ rb_gc_mark_movable(mc->owner);
+ rb_gc_mark_movable(mc->mutex);
}
-static size_t
-monitor_memsize(const void *ptr)
+static void
+monitor_compact(void *ptr)
{
- return sizeof(struct rb_monitor);
+ struct rb_monitor *mc = ptr;
+ mc->owner = rb_gc_location(mc->owner);
+ mc->mutex = rb_gc_location(mc->mutex);
}
static const rb_data_type_t monitor_data_type = {
- "monitor",
- {monitor_mark, RUBY_TYPED_DEFAULT_FREE, monitor_memsize,},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED
+ .wrap_struct_name = "monitor",
+ .function = {
+ .dmark = monitor_mark,
+ .dfree = RUBY_TYPED_DEFAULT_FREE,
+ .dsize = NULL, // Fully embeded
+ .dcompact = monitor_compact,
+ },
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
};
static VALUE
@@ -50,10 +57,10 @@ monitor_ptr(VALUE monitor)
return mc;
}
-static int
-mc_owner_p(struct rb_monitor *mc)
+static bool
+mc_owner_p(struct rb_monitor *mc, VALUE current_fiber)
{
- return mc->owner == rb_fiber_current();
+ return mc->owner == current_fiber;
}
/*
@@ -67,17 +74,44 @@ monitor_try_enter(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
- if (!mc_owner_p(mc)) {
+ VALUE current_fiber = rb_fiber_current();
+ if (!mc_owner_p(mc, current_fiber)) {
if (!rb_mutex_trylock(mc->mutex)) {
return Qfalse;
}
- RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current());
+ RB_OBJ_WRITE(monitor, &mc->owner, current_fiber);
mc->count = 0;
}
mc->count += 1;
return Qtrue;
}
+
+struct monitor_args {
+ VALUE monitor;
+ struct rb_monitor *mc;
+ VALUE current_fiber;
+};
+
+static inline void
+monitor_args_init(struct monitor_args *args, VALUE monitor)
+{
+ args->monitor = monitor;
+ args->mc = monitor_ptr(monitor);
+ args->current_fiber = rb_fiber_current();
+}
+
+static void
+monitor_enter0(struct monitor_args *args)
+{
+ if (!mc_owner_p(args->mc, args->current_fiber)) {
+ rb_mutex_lock(args->mc->mutex);
+ RB_OBJ_WRITE(args->monitor, &args->mc->owner, args->current_fiber);
+ args->mc->count = 0;
+ }
+ args->mc->count++;
+}
+
/*
* call-seq:
* enter -> nil
@@ -87,27 +121,44 @@ monitor_try_enter(VALUE monitor)
static VALUE
monitor_enter(VALUE monitor)
{
- struct rb_monitor *mc = monitor_ptr(monitor);
- if (!mc_owner_p(mc)) {
- rb_mutex_lock(mc->mutex);
- RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current());
- mc->count = 0;
- }
- mc->count++;
+ struct monitor_args args;
+ monitor_args_init(&args, monitor);
+ monitor_enter0(&args);
return Qnil;
}
+static inline void
+monitor_check_owner0(struct monitor_args *args)
+{
+ if (!mc_owner_p(args->mc, args->current_fiber)) {
+ rb_raise(rb_eThreadError, "current fiber not owner");
+ }
+}
+
/* :nodoc: */
static VALUE
monitor_check_owner(VALUE monitor)
{
- struct rb_monitor *mc = monitor_ptr(monitor);
- if (!mc_owner_p(mc)) {
- rb_raise(rb_eThreadError, "current fiber not owner");
- }
+ struct monitor_args args;
+ monitor_args_init(&args, monitor);
+ monitor_check_owner0(&args);
return Qnil;
}
+static void
+monitor_exit0(struct monitor_args *args)
+{
+ monitor_check_owner(args->monitor);
+
+ if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count);
+ args->mc->count--;
+
+ if (args->mc->count == 0) {
+ RB_OBJ_WRITE(args->monitor, &args->mc->owner, Qnil);
+ rb_mutex_unlock(args->mc->mutex);
+ }
+}
+
/*
* call-seq:
* exit -> nil
@@ -117,17 +168,9 @@ monitor_check_owner(VALUE monitor)
static VALUE
monitor_exit(VALUE monitor)
{
- monitor_check_owner(monitor);
-
- struct rb_monitor *mc = monitor_ptr(monitor);
-
- if (mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)mc->count);
- mc->count--;
-
- if (mc->count == 0) {
- RB_OBJ_WRITE(monitor, &mc->owner, Qnil);
- rb_mutex_unlock(mc->mutex);
- }
+ struct monitor_args args;
+ monitor_args_init(&args, monitor);
+ monitor_exit0(&args);
return Qnil;
}
@@ -144,7 +187,7 @@ static VALUE
monitor_owned_p(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
- return (rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc)) ? Qtrue : Qfalse;
+ return rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_fiber_current()) ? Qtrue : Qfalse;
}
static VALUE
@@ -210,9 +253,10 @@ monitor_sync_body(VALUE monitor)
}
static VALUE
-monitor_sync_ensure(VALUE monitor)
+monitor_sync_ensure(VALUE v_args)
{
- return monitor_exit(monitor);
+ monitor_exit0((struct monitor_args *)v_args);
+ return Qnil;
}
/*
@@ -226,8 +270,10 @@ monitor_sync_ensure(VALUE monitor)
static VALUE
monitor_synchronize(VALUE monitor)
{
- monitor_enter(monitor);
- return rb_ensure(monitor_sync_body, monitor, monitor_sync_ensure, monitor);
+ struct monitor_args args;
+ monitor_args_init(&args, monitor);
+ monitor_enter0(&args);
+ return rb_ensure(monitor_sync_body, (VALUE)&args, monitor_sync_ensure, (VALUE)&args);
}
void
diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb
index 3202b10296f549..2c2319e7a38e67 100644
--- a/ext/psych/lib/psych/versions.rb
+++ b/ext/psych/lib/psych/versions.rb
@@ -2,7 +2,7 @@
module Psych
# The version of Psych you are using
- VERSION = '5.2.6'
+ VERSION = '5.3.0'
if RUBY_ENGINE == 'jruby'
DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze
diff --git a/ext/win32/win32-registry.gemspec b/ext/win32/win32-registry.gemspec
index 9b65af4a5b9976..9bd57bd7d1368f 100644
--- a/ext/win32/win32-registry.gemspec
+++ b/ext/win32/win32-registry.gemspec
@@ -1,7 +1,7 @@
# frozen_string_literal: true
Gem::Specification.new do |spec|
spec.name = "win32-registry"
- spec.version = "0.1.1"
+ spec.version = "0.1.2"
spec.authors = ["U.Nakamura"]
spec.email = ["usa@garbagecollect.jp"]
diff --git a/gc.c b/gc.c
index 79eec5d96bf7af..5f0f2307c8c9fd 100644
--- a/gc.c
+++ b/gc.c
@@ -2122,14 +2122,9 @@ rb_gc_obj_free_vm_weak_references(VALUE obj)
static VALUE
id2ref(VALUE objid)
{
-#if SIZEOF_LONG == SIZEOF_VOIDP
-#define NUM2PTR(x) NUM2ULONG(x)
-#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
-#define NUM2PTR(x) NUM2ULL(x)
-#endif
objid = rb_to_int(objid);
if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) {
- VALUE ptr = NUM2PTR(objid);
+ VALUE ptr = (VALUE)NUM2PTR(objid);
if (SPECIAL_CONST_P(ptr)) {
if (ptr == Qtrue) return Qtrue;
if (ptr == Qfalse) return Qfalse;
diff --git a/include/ruby/internal/arithmetic/intptr_t.h b/include/ruby/internal/arithmetic/intptr_t.h
index a354f4469cdf95..70090f88e6b37c 100644
--- a/include/ruby/internal/arithmetic/intptr_t.h
+++ b/include/ruby/internal/arithmetic/intptr_t.h
@@ -32,6 +32,18 @@
#define rb_int_new rb_int2inum /**< @alias{rb_int2inum} */
#define rb_uint_new rb_uint2inum /**< @alias{rb_uint2inum} */
+// These definitions are same as fiddle/conversions.h
+#if SIZEOF_VOIDP <= SIZEOF_LONG
+# define PTR2NUM(x) (LONG2NUM((long)(x)))
+# define NUM2PTR(x) ((void*)(NUM2ULONG(x)))
+#elif SIZEOF_VOIDP <= SIZEOF_LONG_LONG
+# define PTR2NUM(x) (LL2NUM((LONG_LONG)(x)))
+# define NUM2PTR(x) ((void*)(NUM2ULL(x)))
+#else
+// should have been an error in ruby/internal/value.h
+# error Need integer for VALUE
+#endif
+
RBIMPL_SYMBOL_EXPORT_BEGIN()
/**
diff --git a/internal/box.h b/internal/box.h
index 72263cc9dc5e5d..b62b6a9bc946f2 100644
--- a/internal/box.h
+++ b/internal/box.h
@@ -3,16 +3,6 @@
#include "ruby/ruby.h" /* for VALUE */
-#if SIZEOF_VALUE <= SIZEOF_LONG
-# define SVALUE2NUM(x) LONG2NUM((long)(x))
-# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LONG(x)
-#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG
-# define SVALUE2NUM(x) LL2NUM((LONG_LONG)(x))
-# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LL(x)
-#else
-# error Need integer for VALUE
-#endif
-
/**
* @author Ruby developers
* @copyright This file is a part of the programming language Ruby.
diff --git a/lib/forwardable.rb b/lib/forwardable.rb
index 76267c2cd18c57..175d6d9c6b6858 100644
--- a/lib/forwardable.rb
+++ b/lib/forwardable.rb
@@ -109,10 +109,8 @@
# +delegate.rb+.
#
module Forwardable
- require 'forwardable/impl'
-
# Version of +forwardable.rb+
- VERSION = "1.3.3"
+ VERSION = "1.4.0"
VERSION.freeze
# Version for backward compatibility
@@ -206,37 +204,33 @@ def self._delegator_method(obj, accessor, method, ali)
if Module === obj ?
obj.method_defined?(accessor) || obj.private_method_defined?(accessor) :
obj.respond_to?(accessor, true)
- accessor = "#{accessor}()"
+ accessor = "(#{accessor}())"
end
args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block'
method_call = ".__send__(:#{method}, #{args})"
- if _valid_method?(method)
+ if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/)
loc, = caller_locations(2,1)
pre = "_ ="
mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method "
- method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}"
- begin;
- unless defined? _.#{method}
- ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
- _#{method_call}
- else
- _.#{method}(#{args})
- end
- end;
+ method_call = <<~RUBY.chomp
+ if defined?(_.#{method})
+ _.#{method}(#{args})
+ else
+ ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
+ _#{method_call}
+ end
+ RUBY
end
- _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1)
- begin;
+ eval(<<~RUBY, nil, __FILE__, __LINE__ + 1)
proc do
def #{ali}(#{args})
- #{pre}
- begin
- #{accessor}
- end#{method_call}
+ #{pre}#{accessor}
+ #{method_call}
end
end
- end;
+ RUBY
end
end
diff --git a/lib/forwardable/forwardable.gemspec b/lib/forwardable/forwardable.gemspec
index 9ad59c5f8a8830..1b539bcfcb3daf 100644
--- a/lib/forwardable/forwardable.gemspec
+++ b/lib/forwardable/forwardable.gemspec
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.required_ruby_version = '>= 2.4.0'
- spec.files = ["forwardable.gemspec", "lib/forwardable.rb", "lib/forwardable/impl.rb"]
+ spec.files = ["forwardable.gemspec", "lib/forwardable.rb"]
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]
diff --git a/lib/forwardable/impl.rb b/lib/forwardable/impl.rb
deleted file mode 100644
index 0322c136db4a64..00000000000000
--- a/lib/forwardable/impl.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Forwardable
- # :stopdoc:
-
- def self._valid_method?(method)
- catch {|tag|
- eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__)
- }
- rescue SyntaxError
- false
- else
- true
- end
-
- def self._compile_method(src, file, line)
- eval(src, nil, file, line)
- end
-end
diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb
index 513b7778a92f0b..725ff309d27f48 100644
--- a/lib/ipaddr.rb
+++ b/lib/ipaddr.rb
@@ -41,7 +41,7 @@
class IPAddr
# The version string
- VERSION = "1.2.7"
+ VERSION = "1.2.8"
# 32 bit mask for IPv4
IN4MASK = 0xffffffff
diff --git a/lib/optparse.rb b/lib/optparse.rb
index aae73c86b32787..97178e284bd53c 100644
--- a/lib/optparse.rb
+++ b/lib/optparse.rb
@@ -426,7 +426,7 @@
#
class OptionParser
# The version string
- VERSION = "0.8.0"
+ VERSION = "0.8.1"
# An alias for compatibility
Version = VERSION
diff --git a/lib/resolv.rb b/lib/resolv.rb
index b98c7ecdd2aa32..0e62aaf8510496 100644
--- a/lib/resolv.rb
+++ b/lib/resolv.rb
@@ -35,7 +35,7 @@
class Resolv
# The version string
- VERSION = "0.6.3"
+ VERSION = "0.7.0"
##
# Looks up the first IP address for +name+.
diff --git a/load.c b/load.c
index 466517f465b098..144f095b04d2d3 100644
--- a/load.c
+++ b/load.c
@@ -1345,7 +1345,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa
reset_ext_config = true;
ext_config_push(th, &prev_ext_config);
handle = rb_vm_call_cfunc_in_box(box->top_self, load_ext, path, fname, path, box);
- rb_hash_aset(box->ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle));
+ rb_hash_aset(box->ruby_dln_libmap, path, PTR2NUM(handle));
break;
}
result = TAG_RETURN;
@@ -1666,7 +1666,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol)
if (NIL_P(handle)) {
return NULL;
}
- return dln_symbol((void *)NUM2SVALUE(handle), symbol);
+ return dln_symbol(NUM2PTR(handle), symbol);
}
void
diff --git a/spec/ruby/optional/capi/ext/digest_spec.c b/spec/ruby/optional/capi/ext/digest_spec.c
index 9993238cf227d3..65c8defa20adce 100644
--- a/spec/ruby/optional/capi/ext/digest_spec.c
+++ b/spec/ruby/optional/capi/ext/digest_spec.c
@@ -135,7 +135,9 @@ VALUE digest_spec_context_size(VALUE self, VALUE meta) {
return SIZET2NUM(algo->ctx_size);
}
+#ifndef PTR2NUM
#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
+#endif
VALUE digest_spec_context(VALUE self, VALUE digest) {
return PTR2NUM(context);
diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb
index 257e4f1736a2d0..3f0fb7522dc671 100644
--- a/test/json/json_parser_test.rb
+++ b/test/json/json_parser_test.rb
@@ -164,6 +164,14 @@ def test_parse_complex_objects
end
end
+ def test_parse_control_chars_in_string
+ 0.upto(31) do |ord|
+ assert_raise JSON::ParserError do
+ parse(%("#{ord.chr}"))
+ end
+ end
+ end
+
def test_parse_arrays
assert_equal([1,2,3], parse('[1,2,3]'))
assert_equal([1.2,2,3], parse('[1.2,2,3]'))
diff --git a/thread_sync.c b/thread_sync.c
index 8967e24e341bad..9fb1639e9b7dd2 100644
--- a/thread_sync.c
+++ b/thread_sync.c
@@ -239,23 +239,34 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex)
}
static void
-mutex_set_owner(VALUE self, rb_thread_t *th, rb_fiber_t *fiber)
+mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber)
{
- rb_mutex_t *mutex = mutex_ptr(self);
-
mutex->thread = th->self;
mutex->fiber_serial = rb_fiber_serial(fiber);
}
static void
-mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self)
+mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber)
{
- rb_mutex_t *mutex = mutex_ptr(self);
-
- mutex_set_owner(self, th, fiber);
+ mutex_set_owner(mutex, th, fiber);
thread_mutex_insert(th, mutex);
}
+static inline bool
+mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber)
+{
+ if (mutex->fiber_serial == 0) {
+ RUBY_DEBUG_LOG("%p ok", mutex);
+
+ mutex_locked(mutex, th, fiber);
+ return true;
+ }
+ else {
+ RUBY_DEBUG_LOG("%p ng", mutex);
+ return false;
+ }
+}
+
/*
* call-seq:
* mutex.try_lock -> true or false
@@ -266,21 +277,7 @@ mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self)
VALUE
rb_mutex_trylock(VALUE self)
{
- rb_mutex_t *mutex = mutex_ptr(self);
-
- if (mutex->fiber_serial == 0) {
- RUBY_DEBUG_LOG("%p ok", mutex);
-
- rb_fiber_t *fiber = GET_EC()->fiber_ptr;
- rb_thread_t *th = GET_THREAD();
-
- mutex_locked(th, fiber, self);
- return Qtrue;
- }
- else {
- RUBY_DEBUG_LOG("%p ng", mutex);
- return Qfalse;
- }
+ return RBOOL(mutex_trylock(mutex_ptr(self), GET_THREAD(), GET_EC()->fiber_ptr));
}
static VALUE
@@ -321,7 +318,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
rb_raise(rb_eThreadError, "can't be called from trap context");
}
- if (rb_mutex_trylock(self) == Qfalse) {
+ if (!mutex_trylock(mutex, th, fiber)) {
if (mutex->fiber_serial == rb_fiber_serial(fiber)) {
rb_raise(rb_eThreadError, "deadlock; recursive locking");
}
@@ -342,7 +339,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter);
if (!mutex->fiber_serial) {
- mutex_set_owner(self, th, fiber);
+ mutex_set_owner(mutex, th, fiber);
}
}
else {
@@ -383,7 +380,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
// unlocked by another thread while sleeping
if (!mutex->fiber_serial) {
- mutex_set_owner(self, th, fiber);
+ mutex_set_owner(mutex, th, fiber);
}
rb_ractor_sleeper_threads_dec(th->ractor);
@@ -402,7 +399,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
}
RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */
if (!mutex->fiber_serial) {
- mutex_set_owner(self, th, fiber);
+ mutex_set_owner(mutex, th, fiber);
}
}
else {
@@ -421,7 +418,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
}
if (saved_ints) th->ec->interrupt_flag = saved_ints;
- if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(th, fiber, self);
+ if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(mutex, th, fiber);
}
RUBY_DEBUG_LOG("%p locked", mutex);
diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb
index 780f923b55598e..6945c6cdce52a6 100755
--- a/tool/sync_default_gems.rb
+++ b/tool/sync_default_gems.rb
@@ -427,7 +427,7 @@ def sync_default_gems(gem)
end
def check_prerelease_version(gem)
- return if ["rubygems", "mmtk", "cgi"].include?(gem)
+ return if ["rubygems", "mmtk", "cgi", "pathname"].include?(gem)
require "net/https"
require "json"
diff --git a/vm_core.h b/vm_core.h
index 0a1914f57a955b..4195ea5e59c9a4 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -2084,9 +2084,9 @@ rb_current_execution_context(bool expect_ec)
* and the address of the `ruby_current_ec` can be stored on a function
* frame. However, this address can be mis-used after native thread
* migration of a coroutine.
- * 1) Get `ptr =&ruby_current_ec` op NT1 and store it on the frame.
+ * 1) Get `ptr = &ruby_current_ec` on NT1 and store it on the frame.
* 2) Context switch and resume it on the NT2.
- * 3) `ptr` is used on NT2 but it accesses to the TLS on NT1.
+ * 3) `ptr` is used on NT2 but it accesses the TLS of NT1.
* This assertion checks such misusage.
*
* To avoid accidents, `GET_EC()` should be called once on the frame.
diff --git a/vm_debug.h b/vm_debug.h
index d0bc81574a31b5..cf80232f3a5eb0 100644
--- a/vm_debug.h
+++ b/vm_debug.h
@@ -88,6 +88,10 @@ void ruby_debug_log(const char *file, int line, const char *func_name, const cha
void ruby_debug_log_print(unsigned int n);
bool ruby_debug_log_filter(const char *func_name, const char *file_name);
+// convenient macro to log even if the USE_RUBY_DEBUG_LOG macro is not specified.
+// You can use this macro for temporary usage (you should not commit it).
+#define _RUBY_DEBUG_LOG(...) ruby_debug_log(__FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING, "" __VA_ARGS__)
+
#if RBIMPL_COMPILER_IS(GCC) && defined(__OPTIMIZE__)
# define ruby_debug_log(...) \
RB_GNUC_EXTENSION_BLOCK( \
@@ -97,10 +101,6 @@ bool ruby_debug_log_filter(const char *func_name, const char *file_name);
RBIMPL_WARNING_POP())
#endif
-// convenient macro to log even if the USE_RUBY_DEBUG_LOG macro is not specified.
-// You can use this macro for temporary usage (you should not commit it).
-#define _RUBY_DEBUG_LOG(...) ruby_debug_log(__FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING, "" __VA_ARGS__)
-
#if USE_RUBY_DEBUG_LOG
# define RUBY_DEBUG_LOG_ENABLED(func_name, file_name) \
(ruby_debug_log_mode && ruby_debug_log_filter(func_name, file_name))
diff --git a/yjit.c b/yjit.c
index 6d909a0da61ee3..f3c256093eda0b 100644
--- a/yjit.c
+++ b/yjit.c
@@ -64,8 +64,6 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);
// The "_yjit_" part is for trying to be informative. We might want different
// suffixes for symbols meant for Rust and symbols meant for broader CRuby.
-# define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
-
// For a given raw_sample (frame), set the hash with the caller's
// name, file, and line number. Return the hash with collected frame_info.
static void
diff --git a/zjit.c b/zjit.c
index 75cca281a45a91..05fb3e1f028ff6 100644
--- a/zjit.c
+++ b/zjit.c
@@ -31,12 +31,14 @@
#include
+// This build config impacts the pointer tagging scheme and we only want to
+// support one scheme for simplicity.
+STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);
+
enum zjit_struct_offsets {
ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param)
};
-#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
-
// For a given raw_sample (frame), set the hash with the caller's
// name, file, and line number. Return the hash with collected frame_info.
static void