Skip to content

Commit 3aeff4f

Browse files
committed
[GR-32332] Backports for 21.2 batch 3
PullRequest: truffleruby/2793
2 parents f516172 + 18213b6 commit 3aeff4f

File tree

17 files changed

+211
-32
lines changed

17 files changed

+211
-32
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Bug fixes:
1212
* Fix `File.{atime, mtime, ctime}` to include nanoseconds (#2337).
1313
* Fix `Array#[a, b] = "frozen string literal".freeze` (#2355).
1414
* `rb_funcall()` now releases the C-extension lock (similar to MRI).
15+
* Fix `rb_str_modify_expand` to preserve existing bytes (#2392).
16+
* Fix `Marshal.load` of multiple `Symbols` with an explicit encoding (#1624).
17+
* Fix `String#scrub` when replacement is frozen (#2398, @LillianZ).
1518

1619
Compatibility:
1720

@@ -37,6 +40,7 @@ Compatibility:
3740
* Implement `rb_backref_set`.
3841
* Fix `Float#<=>` when comparing `Infinity` to other `#infinite?` values.
3942
* Implement `date` library as a C extension to improve compatibility (#2344).
43+
* Update `rb_str_modify` and `rb_str_modify_expand` to raise a `FrozenError` when given a frozen string (#2392).
4044

4145
Performance:
4246

lib/cext/ABI_check.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2
1+
4

lib/cext/ABI_version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
21.2.0

lib/cext/include/truffleruby/truffleruby-pre.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ extern ID (*rb_tr_sym2id)(VALUE sym);
6969
#define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field))
7070
#endif
7171

72+
// Defines
73+
74+
// To support racc releases before https://github.com/ruby/racc/pull/165
75+
#define HAVE_RB_BLOCK_CALL
76+
7277
#if defined(__cplusplus)
7378
}
7479
#endif

spec/ruby/core/marshal/dump_spec.rb

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@
7878
s = "\u2192".force_encoding("binary").to_sym
7979
Marshal.dump(s).should == "\x04\b:\b\xE2\x86\x92"
8080
end
81+
82+
it "dumps multiple Symbols sharing the same encoding" do
83+
# Note that the encoding is a link for the second Symbol
84+
symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
85+
symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
86+
value = [
87+
"€a".force_encoding(Encoding::UTF_8).to_sym,
88+
"€b".force_encoding(Encoding::UTF_8).to_sym
89+
]
90+
Marshal.dump(value).should == "\x04\b[\a#{symbol1}#{symbol2}"
91+
92+
value = [*value, value[0]]
93+
Marshal.dump(value).should == "\x04\b[\b#{symbol1}#{symbol2};\x00"
94+
end
8195
end
8296

8397
describe "with an object responding to #marshal_dump" do
@@ -343,8 +357,13 @@
343357
end
344358

345359
it "dumps an extended Struct" do
346-
st = Struct.new("Extended", :a, :b).new
347-
Marshal.dump(st.extend(Meths)).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
360+
obj = Struct.new("Extended", :a, :b).new.extend(Meths)
361+
Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
362+
363+
s = 'hi'
364+
obj.a = [:a, s]
365+
obj.b = [:Meths, s]
366+
Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
348367
Struct.send(:remove_const, :Extended)
349368
end
350369
end

spec/ruby/core/marshal/fixtures/marshal_data.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class UserPreviouslyDefinedWithInitializedIvar
8383
end
8484

8585
class UserMarshal
86-
attr_reader :data
86+
attr_accessor :data
8787

8888
def initialize
8989
@data = 'stuff'

spec/ruby/core/marshal/shared/load.rb

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@
309309

310310
it "loads an extended Array object containing a user-marshaled object" do
311311
obj = [UserMarshal.new, UserMarshal.new].extend(Meths)
312-
new_obj = Marshal.send(@method, "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT")
312+
dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT"
313+
new_obj = Marshal.send(@method, dump)
313314

314315
new_obj.should == obj
315316
obj_ancestors = class << obj; ancestors[1..-1]; end
@@ -399,6 +400,24 @@
399400
sym.should == s
400401
sym.encoding.should == Encoding::BINARY
401402
end
403+
404+
it "loads multiple Symbols sharing the same encoding" do
405+
# Note that the encoding is a link for the second Symbol
406+
symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
407+
symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
408+
dump = "\x04\b[\a#{symbol1}#{symbol2}"
409+
value = Marshal.send(@method, dump)
410+
value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8]
411+
expected = [
412+
"€a".force_encoding(Encoding::UTF_8).to_sym,
413+
"€b".force_encoding(Encoding::UTF_8).to_sym
414+
]
415+
value.should == expected
416+
417+
value = Marshal.send(@method, "\x04\b[\b#{symbol1}#{symbol2};\x00")
418+
value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
419+
value.should == [*expected, expected[0]]
420+
end
402421
end
403422

404423
describe "for a String" do
@@ -460,20 +479,23 @@
460479
describe "for a Struct" do
461480
it "loads a extended_struct having fields with same objects" do
462481
s = 'hi'
463-
obj = Struct.new("Ure2", :a, :b).new.extend(Meths)
482+
obj = Struct.new("Extended", :a, :b).new.extend(Meths)
483+
dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
484+
Marshal.send(@method, dump).should == obj
485+
464486
obj.a = [:a, s]
465487
obj.b = [:Meths, s]
466-
467-
Marshal.send(@method,
468-
"\004\be:\nMethsS:\021Struct::Ure2\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
469-
).should == obj
470-
Struct.send(:remove_const, :Ure2)
488+
dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
489+
Marshal.send(@method, dump).should == obj
490+
Struct.send(:remove_const, :Extended)
471491
end
472492

473493
it "loads a struct having ivar" do
474494
obj = Struct.new("Thick").new
475495
obj.instance_variable_set(:@foo, 5)
476-
Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n").should == obj
496+
reloaded = Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n")
497+
reloaded.should == obj
498+
reloaded.instance_variable_get(:@foo).should == 5
477499
Struct.send(:remove_const, :Thick)
478500
end
479501

@@ -588,6 +610,18 @@
588610
end
589611
end
590612

613+
describe "for an object responding to #marshal_dump and #marshal_load" do
614+
it "loads a user-marshaled object" do
615+
obj = UserMarshal.new
616+
obj.data = :data
617+
value = [obj, :data]
618+
dump = Marshal.dump(value)
619+
dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06"
620+
reloaded = Marshal.load(dump)
621+
reloaded.should == value
622+
end
623+
end
624+
591625
describe "for a user object" do
592626
it "loads a user-marshaled extended object" do
593627
obj = UserMarshal.new.extend(Meths)

spec/ruby/core/string/scrub_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,10 @@
119119
input.scrub!
120120
input.instance_variable_get(:@a).should == 'b'
121121
end
122+
123+
it "accepts a frozen string as a replacement" do
124+
input = "a\xE2"
125+
input.scrub!('.'.freeze)
126+
input.should == 'a.'
127+
end
122128
end

spec/ruby/optional/capi/ext/string_spec.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "ruby.h"
22
#include "rubyspec.h"
33

4+
#include <fcntl.h>
45
#include <string.h>
56
#include <stdarg.h>
67

@@ -279,6 +280,16 @@ VALUE string_spec_rb_str_resize_RSTRING_LEN(VALUE self, VALUE str, VALUE size) {
279280
return INT2FIX(RSTRING_LEN(modified));
280281
}
281282

283+
VALUE string_spec_rb_str_resize_copy(VALUE self, VALUE str) {
284+
rb_str_modify_expand(str, 5);
285+
char *buffer = RSTRING_PTR(str);
286+
buffer[1] = 'e';
287+
buffer[2] = 's';
288+
buffer[3] = 't';
289+
rb_str_resize(str, 4);
290+
return str;
291+
}
292+
282293
VALUE string_spec_rb_str_split(VALUE self, VALUE str) {
283294
return rb_str_split(str, ",");
284295
}
@@ -374,6 +385,20 @@ VALUE string_spec_RSTRING_PTR_after_yield(VALUE self, VALUE str) {
374385
return from_rstring_ptr;
375386
}
376387

388+
VALUE string_spec_RSTRING_PTR_read(VALUE self, VALUE str, VALUE path) {
389+
char *cpath = StringValueCStr(path);
390+
int fd = open(cpath, O_RDONLY);
391+
rb_str_modify_expand(str, 10);
392+
char *buffer = RSTRING_PTR(str);
393+
read(fd, buffer, 10);
394+
rb_str_modify_expand(str, 21);
395+
char *buffer2 = RSTRING_PTR(str);
396+
read(fd, buffer2 + 10, 11);
397+
rb_str_set_len(str, 21);
398+
close(fd);
399+
return str;
400+
}
401+
377402
VALUE string_spec_StringValue(VALUE self, VALUE str) {
378403
return StringValue(str);
379404
}
@@ -527,6 +552,7 @@ void Init_string_spec(void) {
527552
rb_define_method(cls, "rb_str_modify_expand", string_spec_rb_str_modify_expand, 2);
528553
rb_define_method(cls, "rb_str_resize", string_spec_rb_str_resize, 2);
529554
rb_define_method(cls, "rb_str_resize_RSTRING_LEN", string_spec_rb_str_resize_RSTRING_LEN, 2);
555+
rb_define_method(cls, "rb_str_resize_copy", string_spec_rb_str_resize_copy, 1);
530556
rb_define_method(cls, "rb_str_set_len", string_spec_rb_str_set_len, 2);
531557
rb_define_method(cls, "rb_str_set_len_RSTRING_LEN", string_spec_rb_str_set_len_RSTRING_LEN, 2);
532558
rb_define_method(cls, "rb_str_split", string_spec_rb_str_split, 1);
@@ -542,6 +568,7 @@ void Init_string_spec(void) {
542568
rb_define_method(cls, "RSTRING_PTR_set", string_spec_RSTRING_PTR_set, 3);
543569
rb_define_method(cls, "RSTRING_PTR_after_funcall", string_spec_RSTRING_PTR_after_funcall, 2);
544570
rb_define_method(cls, "RSTRING_PTR_after_yield", string_spec_RSTRING_PTR_after_yield, 1);
571+
rb_define_method(cls, "RSTRING_PTR_read", string_spec_RSTRING_PTR_read, 2);
545572
rb_define_method(cls, "StringValue", string_spec_StringValue, 1);
546573
rb_define_method(cls, "SafeStringValue", string_spec_SafeStringValue, 1);
547574
rb_define_method(cls, "rb_str_hash", string_spec_rb_str_hash, 1);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fixture file contents

spec/ruby/optional/capi/string_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,12 @@ def inspect
598598
str = " "
599599
@s.RSTRING_PTR_short_memcpy(str).should == "Infinity"
600600
end
601+
602+
it "allows read to update string contents" do
603+
filename = fixture(__FILE__, "read.txt")
604+
str = ""
605+
@s.RSTRING_PTR_read(str, filename).should == "fixture file contents"
606+
end
601607
end
602608

603609
describe "RSTRING_LEN" do
@@ -665,6 +671,12 @@ def inspect
665671
end
666672
end
667673

674+
describe "rb_str_modify" do
675+
it "raises an error if the string is frozen" do
676+
-> { @s.rb_str_modify("frozen".freeze) }.should raise_error(FrozenError)
677+
end
678+
end
679+
668680
describe "rb_str_modify_expand" do
669681
it "grows the capacity to bytesize + expand, not changing the bytesize" do
670682
str = @s.rb_str_buf_new(256, "abcd")
@@ -684,6 +696,15 @@ def inspect
684696
str.bytesize.should == 3
685697
@s.RSTRING_LEN(str).should == 3
686698
@s.rb_str_capacity(str).should == 1027
699+
700+
@s.rb_str_modify_expand(str, 1)
701+
str.bytesize.should == 3
702+
@s.RSTRING_LEN(str).should == 3
703+
@s.rb_str_capacity(str).should == 4
704+
end
705+
706+
it "raises an error if the string is frozen" do
707+
-> { @s.rb_str_modify_expand("frozen".freeze, 10) }.should raise_error(FrozenError)
687708
end
688709
end
689710

@@ -700,6 +721,11 @@ def inspect
700721
@s.rb_str_resize_RSTRING_LEN("test", 2).should == 2
701722
end
702723

724+
it "copies the existing bytes" do
725+
str = "t"
726+
@s.rb_str_resize_copy(str).should == "test"
727+
end
728+
703729
it "increases the size of the string" do
704730
expected = "test".force_encoding("US-ASCII")
705731
str = @s.rb_str_resize(expected.dup, 12)

spec/truffle/identity_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@
6767
RbConfig::CONFIG['RUBY_INSTALL_NAME'].should == 'truffleruby'
6868
end
6969

70-
it "RbConfig::CONFIG['ruby_version'] is the ABI version and starts with RUBY_VERSION and has an extra component" do
71-
RbConfig::CONFIG['ruby_version'].should =~ /\A#{Regexp.escape RUBY_VERSION}\.\d+\z/
70+
it "RbConfig::CONFIG['ruby_version'] is the ABI version and starts with RUBY_VERSION and has at least an extra component" do
71+
RbConfig::CONFIG['ruby_version'].should =~ /\A#{Regexp.escape RUBY_VERSION}\.\d+(\.\d+)*\z/
7272
end
7373

7474
it "RbConfig::CONFIG['RUBY_BASE_NAME'] is 'ruby'" do

src/main/c/cext/string.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ VALUE rb_str_split(VALUE string, const char *split) {
174174
}
175175

176176
void rb_str_modify(VALUE string) {
177+
rb_check_frozen(string);
177178
ENC_CODERANGE_CLEAR(string);
178179
}
179180

@@ -337,12 +338,9 @@ void rb_str_modify_expand(VALUE str, long expand) {
337338
rb_raise(rb_eArgError, "string size too big");
338339
}
339340

341+
rb_check_frozen(str);
340342
if (expand > 0) {
341-
// rb_str_modify_expand() resizes the native buffer but does not change
342-
// RSTRING_LEN() (and therefore String#bytesize).
343-
// TODO (eregon, 26 Apr 2018): Do this more directly.
344-
rb_str_resize(str, len + expand);
345-
rb_str_set_len(str, len);
343+
polyglot_invoke(RUBY_CEXT, "rb_tr_str_capa_resize", rb_tr_unwrap(str), len + expand);
346344
}
347345

348346
ENC_CODERANGE_CLEAR(str);

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,28 @@ protected RubyString rbStrResize(RubyString string, int newByteLength,
666666
}
667667
}
668668

669+
@CoreMethod(names = "rb_tr_str_capa_resize", onSingleton = true, required = 2, lowerFixnum = 2)
670+
public abstract static class TrStrCapaResizeNode extends CoreMethodArrayArgumentsNode {
671+
672+
@Specialization
673+
protected RubyString trStrCapaResize(RubyString string, int newCapacity,
674+
@Cached StringToNativeNode stringToNativeNode,
675+
@Cached ConditionProfile asciiOnlyProfile) {
676+
final NativeRope nativeRope = stringToNativeNode.executeToNative(string);
677+
678+
if (nativeRope.getCapacity() == newCapacity) {
679+
return string;
680+
} else {
681+
final NativeRope newRope = nativeRope
682+
.expandCapacity(getContext(), newCapacity);
683+
string.setRope(newRope);
684+
return string;
685+
}
686+
}
687+
688+
}
689+
690+
669691
@CoreMethod(names = "rb_block_proc", onSingleton = true)
670692
public abstract static class BlockProcNode extends CoreMethodArrayArgumentsNode {
671693

src/main/java/org/truffleruby/core/rope/NativeRope.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.oracle.truffle.api.profiles.ConditionProfile;
1313
import org.jcodings.Encoding;
1414
import org.jcodings.specific.ASCIIEncoding;
15+
import org.truffleruby.RubyContext;
1516
import org.truffleruby.core.FinalizationService;
1617
import org.truffleruby.core.string.StringAttributes;
1718
import org.truffleruby.core.string.StringSupport;
@@ -88,12 +89,31 @@ public NativeRope resize(FinalizationService finalizationService, int newByteLen
8889
assert byteLength() != newByteLength;
8990

9091
final Pointer pointer = Pointer.malloc(newByteLength + 1);
91-
pointer.writeBytes(0, this.pointer, 0, Math.min(byteLength(), newByteLength));
92+
pointer.writeBytes(0, this.pointer, 0, Math.min(getNativePointer().getSize(), newByteLength));
9293
pointer.writeByte(newByteLength, (byte) 0); // Like MRI
9394
pointer.enableAutorelease(finalizationService);
9495
return new NativeRope(pointer, newByteLength, getEncoding(), UNKNOWN_CHARACTER_LENGTH, CodeRange.CR_UNKNOWN);
9596
}
9697

98+
/** Creates a new native rope which preserves existing bytes and byte length up to newCapacity
99+
*
100+
* @param context the Ruby context
101+
* @param newCapacity the size in bytes minus one of the new pointer length
102+
* @return the new NativeRope */
103+
public NativeRope expandCapacity(RubyContext context, int newCapacity) {
104+
assert getCapacity() != newCapacity;
105+
final Pointer pointer = Pointer.malloc(newCapacity + 1);
106+
pointer.writeBytes(0, this.pointer, 0, Math.min(getNativePointer().getSize(), newCapacity));
107+
pointer.writeByte(newCapacity, (byte) 0); // Like MRI
108+
pointer.enableAutorelease(context.getFinalizationService());
109+
return new NativeRope(
110+
pointer,
111+
byteLength(),
112+
getEncoding(),
113+
UNKNOWN_CHARACTER_LENGTH,
114+
CodeRange.CR_UNKNOWN);
115+
}
116+
97117
@Override
98118
public byte[] getBytes() {
99119
// Always re-read bytes from the native pointer as they might have changed.

0 commit comments

Comments
 (0)