Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ node_children(VALUE ast_value, const NODE *node)
: var_name(ainfo->rest_arg)),
(ainfo->no_kwarg ? Qfalse : NEW_CHILD(ast_value, (NODE *)ainfo->kw_args)),
(ainfo->no_kwarg ? Qfalse : NEW_CHILD(ast_value, ainfo->kw_rest_arg)),
var_name(ainfo->block_arg));
(ainfo->no_blockarg ? Qfalse : var_name(ainfo->block_arg)));
}
case NODE_SCOPE:
{
Expand Down
49 changes: 42 additions & 7 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -5945,13 +5945,48 @@ rb_f_test(int argc, VALUE *argv, VALUE _)
/*
* Document-class: File::Stat
*
* Objects of class File::Stat encapsulate common status information
* for File objects. The information is recorded at the moment the
* File::Stat object is created; changes made to the file after that
* point will not be reflected. File::Stat objects are returned by
* IO#stat, File::stat, File#lstat, and File::lstat. Many of these
* methods return platform-specific values, and not all values are
* meaningful on all systems. See also Kernel#test.
* A \File::Stat object contains information about an entry in the file system.
*
* Each of these methods returns a new \File::Stat object:
*
* - File#lstat.
* - File::Stat.new.
* - File::lstat.
* - File::stat.
* - IO#stat.
*
* === Snapshot
*
* A new \File::Stat object takes an immediate "snapshot" of the entry's information;
* the captured information is never updated,
* regardless of changes in the actual entry:
*
* The entry must exist when File::Stat.new is called:
*
* filepath = 't.tmp'
* File.exist?(filepath) # => false
* File::Stat.new(filepath) # Raises Errno::ENOENT: No such file or directory.
* File.write(filepath, 'foo') # Create the file.
* stat = File::Stat.new(filepath) # Okay.
*
* Later changes to the actual entry do not change the \File::Stat object:
*
* File.atime(filepath) # => 2026-04-01 11:51:38.0014518 -0500
* stat.atime # => 2026-04-01 11:51:38.0014518 -0500
* File.write(filepath, 'bar')
* File.atime(filepath) # => 2026-04-01 11:58:11.922614 -0500
* stat.atime # => 2026-04-01 11:51:38.0014518 -0500
* File.delete(filepath)
stat.atime # => 2026-04-01 11:51:38.0014518 -0500
*
* === OS-Dependencies
*
* Methods in a \File::Stat object may return platform-dependents values,
* and not all values are meaningful on all systems;
* for example, File::Stat#blocks returns +nil+ on Windows,
* but returns an integer on Linux.
*
* See also Kernel#test.
*/

static VALUE
Expand Down
46 changes: 31 additions & 15 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,30 @@ VALUE rb_cSymbol;

#define STR_ENC_GET(str) get_encoding(str)

static inline bool
zero_filled(const char *s, int n)
{
for (; n > 0; --n) {
if (*s++) return false;
}
return true;
}

#if !defined SHARABLE_MIDDLE_SUBSTRING
# define SHARABLE_MIDDLE_SUBSTRING 0
#endif
#if !SHARABLE_MIDDLE_SUBSTRING
#define SHARABLE_SUBSTRING_P(beg, len, end) ((beg) + (len) == (end))

static inline bool
SHARABLE_SUBSTRING_P(VALUE str, long beg, long len)
{
#if SHARABLE_MIDDLE_SUBSTRING
return true;
#else
#define SHARABLE_SUBSTRING_P(beg, len, end) 1
long end = beg + len;
long source_len = RSTRING_LEN(str);
return end == source_len || zero_filled(RSTRING_PTR(str) + end, TERM_LEN(str));
#endif

}

static inline long
str_embed_capa(VALUE str)
Expand Down Expand Up @@ -2810,15 +2825,6 @@ rb_string_value_ptr(volatile VALUE *ptr)
return RSTRING_PTR(str);
}

static int
zero_filled(const char *s, int n)
{
for (; n > 0; --n) {
if (*s++) return 0;
}
return 1;
}

static const char *
str_null_char(const char *s, long len, const int minlen, rb_encoding *enc)
{
Expand Down Expand Up @@ -3138,8 +3144,11 @@ str_subseq(VALUE str, long beg, long len)
RUBY_ASSERT(beg+len <= RSTRING_LEN(str));

const int termlen = TERM_LEN(str);
if (!SHARABLE_SUBSTRING_P(beg, len, RSTRING_LEN(str))) {
if (!SHARABLE_SUBSTRING_P(str, beg, len)) {
str2 = rb_enc_str_new(RSTRING_PTR(str) + beg, len, rb_str_enc_get(str));
if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT) {
ENC_CODERANGE_SET(str2, ENC_CODERANGE_7BIT);
}
RB_GC_GUARD(str);
return str2;
}
Expand All @@ -3152,12 +3161,19 @@ str_subseq(VALUE str, long beg, long len)
TERM_FILL(ptr2+len, termlen);

STR_SET_LEN(str2, len);
if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT) {
ENC_CODERANGE_SET(str2, ENC_CODERANGE_7BIT);
}

RB_GC_GUARD(str);
}
else {
str_replace_shared(str2, str);
RUBY_ASSERT(!STR_EMBED_P(str2));
ENC_CODERANGE_CLEAR(str2);
if (ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
ENC_CODERANGE_CLEAR(str2);
}

RSTRING(str2)->as.heap.ptr += beg;
if (RSTRING_LEN(str2) > len) {
STR_SET_LEN(str2, len);
Expand Down
1 change: 1 addition & 0 deletions test/ruby/test_ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@ def test_block_arg
assert_equal(nil, block_arg.call(''))
assert_equal(:block, block_arg.call('&block'))
assert_equal(:&, block_arg.call('&'))
assert_equal(false, block_arg.call('&nil'))
end

def test_keyword_rest
Expand Down
21 changes: 21 additions & 0 deletions test/ruby/test_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3461,6 +3461,27 @@ def test_byteslice
assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954)
end

def test_shared_middle_string_terminator
ten = "0123456789"
hundred = ten * 10
str = "#{hundred}\0#{hundred}".freeze

require 'objspace'

substr = str.byteslice(0, hundred.bytesize)
assert_equal hundred, substr
assert_includes ObjectSpace.dump(substr), ' "shared":true,'

# Larger terminator
substr.force_encoding(Encoding::UTF_16BE)
assert_equal hundred.dup.force_encoding(Encoding::UTF_16BE), substr
refute_includes ObjectSpace.dump(substr), ' "shared":true,'

substr = str.byteslice(0, hundred.bytesize + 1)
assert_equal hundred + "\0", substr
refute_includes ObjectSpace.dump(substr), ' "shared":true,'
end

def test_unknown_string_option
str = nil
assert_nothing_raised(SyntaxError) do
Expand Down