Skip to content

Commit 796c0b3

Browse files
committed
Make Data#initialize reject Integer keys
1 parent 6321237 commit 796c0b3

File tree

5 files changed

+116
-121
lines changed

5 files changed

+116
-121
lines changed

array.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6524,7 +6524,7 @@ rb_ary_uniq(VALUE ary)
65246524
* see also {Methods for Deleting}[rdoc-ref:Array@Methods+for+Deleting].
65256525
*/
65266526

6527-
static VALUE
6527+
VALUE
65286528
rb_ary_compact_bang(VALUE ary)
65296529
{
65306530
VALUE *p, *t, *end;

internal/array.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ size_t rb_ary_size_as_embedded(VALUE ary);
3737
void rb_ary_make_embedded(VALUE ary);
3838
bool rb_ary_embeddable_p(VALUE ary);
3939
VALUE rb_ary_diff(VALUE ary1, VALUE ary2);
40+
VALUE rb_ary_compact_bang(VALUE ary);
4041
RUBY_EXTERN VALUE rb_cArray_empty_frozen;
4142

4243
static inline VALUE rb_ary_entry_internal(VALUE ary, long offset);

spec/ruby/core/data/deconstruct_keys_spec.rb

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,6 @@
3434
d.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2}
3535
end
3636

37-
it "accepts argument position number as well but returns them as keys" do
38-
klass = Data.define(:x, :y)
39-
d = klass.new(1, 2)
40-
41-
d.deconstruct_keys([0, 1]).should == {0 => 1, 1 => 2}
42-
d.deconstruct_keys([0] ).should == {0 => 1}
43-
d.deconstruct_keys([-1] ).should == {-1 => 2}
44-
end
45-
46-
it "ignores incorrect position numbers" do
47-
klass = Data.define(:x, :y)
48-
d = klass.new(1, 2)
49-
50-
d.deconstruct_keys([0, 3]).should == {0 => 1}
51-
end
52-
53-
it "support mixing attribute names and argument position numbers" do
54-
klass = Data.define(:x, :y)
55-
d = klass.new(1, 2)
56-
57-
d.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1}
58-
end
59-
6037
it "returns an empty hash when there are more keys than attributes" do
6138
klass = Data.define(:x, :y)
6239
d = klass.new(1, 2)
@@ -72,50 +49,53 @@
7249
d.deconstruct_keys([:x, :a]).should == {x: 1}
7350
end
7451

75-
it "returns at first not existing argument position number" do
76-
klass = Data.define(:x, :y)
77-
d = klass.new(1, 2)
78-
79-
d.deconstruct_keys([3, 0]).should == {}
80-
d.deconstruct_keys([0, 3]).should == {0 => 1}
81-
end
82-
8352
it "accepts nil argument and return all the attributes" do
8453
klass = Data.define(:x, :y)
8554
d = klass.new(1, 2)
8655

8756
d.deconstruct_keys(nil).should == {x: 1, y: 2}
8857
end
8958

90-
it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do
91-
klass = Data.define(:x, :y)
92-
d = klass.new(1, 2)
59+
ruby_bug "Bug #21844", ""..."4.1" do
60+
it "tries to convert a key with #to_str if index is not a String nor a Symbol, but responds to #to_str" do
61+
klass = Data.define(:x, :y)
62+
d = klass.new(1, 2)
9363

94-
key = mock("to_int")
95-
key.should_receive(:to_int).and_return(1)
64+
key = mock("to_str")
65+
key.should_receive(:to_str).and_return("y")
9666

97-
d.deconstruct_keys([key]).should == { key => 2 }
98-
end
67+
d.deconstruct_keys([key]).should == { "y" => 2 }
68+
end
9969

100-
it "raises a TypeError if the conversion with #to_int does not return an Integer" do
101-
klass = Data.define(:x, :y)
102-
d = klass.new(1, 2)
70+
it "raise an error on argument position number" do
71+
klass = Data.define(:x, :y)
72+
d = klass.new(1, 2)
10373

104-
key = mock("to_int")
105-
key.should_receive(:to_int).and_return("not an Integer")
74+
-> {
75+
d.deconstruct_keys([0, 1])
76+
}.should raise_error(TypeError, "0 is not a symbol nor a string")
77+
end
10678

107-
-> {
108-
d.deconstruct_keys([key])
109-
}.should raise_error(TypeError, /can't convert MockObject to Integer/)
110-
end
79+
it "raises a TypeError if the conversion with #to_str does not return a String" do
80+
klass = Data.define(:x, :y)
81+
d = klass.new(1, 2)
11182

112-
it "raises TypeError if index is not a String, a Symbol and not convertible to Integer " do
113-
klass = Data.define(:x, :y)
114-
d = klass.new(1, 2)
83+
key = mock("to_str")
84+
key.should_receive(:to_str).and_return(0)
11585

116-
-> {
117-
d.deconstruct_keys([0, []])
118-
}.should raise_error(TypeError, "no implicit conversion of Array into Integer")
86+
-> {
87+
d.deconstruct_keys([key])
88+
}.should raise_error(TypeError, /can't convert MockObject to String/)
89+
end
90+
91+
it "raises TypeError if index is not a Symbol and not convertible to String " do
92+
klass = Data.define(:x, :y)
93+
d = klass.new(1, 2)
94+
95+
-> {
96+
d.deconstruct_keys([0, []])
97+
}.should raise_error(TypeError, "0 is not a symbol nor a string")
98+
end
11999
end
120100

121101
it "raise TypeError if passed anything except nil or array" do

struct.c

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -716,18 +716,18 @@ num_members(VALUE klass)
716716
struct struct_hash_set_arg {
717717
VALUE self;
718718
VALUE unknown_keywords;
719-
VALUE found_keywords;
719+
VALUE missing_keywords;
720+
long missing_count;
720721
};
721722

722-
static int rb_struct_pos(VALUE s, VALUE *name);
723+
static int rb_struct_pos(VALUE s, VALUE *name, bool name_only);
724+
static VALUE deconstruct_keys(VALUE s, VALUE keys, bool name_only);
725+
static int rb_struct_pos(VALUE s, VALUE *name, bool name_only);
723726

724727
static int
725-
struct_hash_set_i(VALUE key, VALUE val, VALUE arg)
728+
struct_hash_aset(VALUE key, VALUE val, struct struct_hash_set_arg *args, bool name_only)
726729
{
727-
struct struct_hash_set_arg *args = (struct struct_hash_set_arg *)arg;
728-
VALUE klass = rb_obj_class(args->self);
729-
VALUE members = struct_ivar_get(klass, id_members);
730-
int i = rb_struct_pos(args->self, &key);
730+
int i = rb_struct_pos(args->self, &key, name_only);
731731
if (i < 0) {
732732
if (NIL_P(args->unknown_keywords)) {
733733
args->unknown_keywords = rb_ary_new();
@@ -737,13 +737,15 @@ struct_hash_set_i(VALUE key, VALUE val, VALUE arg)
737737
else {
738738
rb_struct_modify(args->self);
739739
RSTRUCT_SET_RAW(args->self, i, val);
740-
741-
if (NIL_P(args->found_keywords)) {
742-
args->found_keywords = rb_hash_new();
743-
}
744-
VALUE member = rb_ary_entry(members, i);
745-
rb_hash_aset(args->found_keywords, member, Qtrue);
746740
}
741+
return i;
742+
}
743+
744+
static int
745+
struct_hash_set_i(VALUE key, VALUE val, VALUE arg)
746+
{
747+
struct struct_hash_set_arg *args = (struct struct_hash_set_arg *)arg;
748+
struct_hash_aset(key, val, args, false);
747749
return ST_CONTINUE;
748750
}
749751

@@ -776,13 +778,13 @@ rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self)
776778
break;
777779
}
778780
if (keyword_init) {
779-
struct struct_hash_set_arg arg;
781+
struct struct_hash_set_arg arg = {
782+
.self = self,
783+
.unknown_keywords = Qnil,
784+
};
780785
rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self), n);
781-
arg.self = self;
782-
arg.unknown_keywords = Qnil;
783-
arg.found_keywords = Qnil;
784786
rb_hash_foreach(argv[0], struct_hash_set_i, (VALUE)&arg);
785-
if (arg.unknown_keywords != Qnil) {
787+
if (UNLIKELY(!NIL_P(arg.unknown_keywords))) {
786788
rb_raise(rb_eArgError, "unknown keywords: %s",
787789
RSTRING_PTR(rb_ary_join(arg.unknown_keywords, rb_str_new2(", "))));
788790
}
@@ -1124,6 +1126,12 @@ rb_struct_to_h(VALUE s)
11241126
*/
11251127
static VALUE
11261128
rb_struct_deconstruct_keys(VALUE s, VALUE keys)
1129+
{
1130+
return deconstruct_keys(s, keys, false);
1131+
}
1132+
1133+
static VALUE
1134+
deconstruct_keys(VALUE s, VALUE keys, bool name_only)
11271135
{
11281136
VALUE h;
11291137
long i;
@@ -1143,7 +1151,7 @@ rb_struct_deconstruct_keys(VALUE s, VALUE keys)
11431151
h = rb_hash_new_with_size(RARRAY_LEN(keys));
11441152
for (i=0; i<RARRAY_LEN(keys); i++) {
11451153
VALUE key = RARRAY_AREF(keys, i);
1146-
int i = rb_struct_pos(s, &key);
1154+
int i = rb_struct_pos(s, &key, name_only);
11471155
if (i < 0) {
11481156
return h;
11491157
}
@@ -1171,15 +1179,15 @@ rb_struct_init_copy(VALUE copy, VALUE s)
11711179
}
11721180

11731181
static int
1174-
rb_struct_pos(VALUE s, VALUE *name)
1182+
rb_struct_pos(VALUE s, VALUE *name, bool name_only)
11751183
{
11761184
long i;
11771185
VALUE idx = *name;
11781186

11791187
if (SYMBOL_P(idx)) {
11801188
return struct_member_pos(s, idx);
11811189
}
1182-
else if (RB_TYPE_P(idx, T_STRING)) {
1190+
else if (name_only || RB_TYPE_P(idx, T_STRING)) {
11831191
idx = rb_check_symbol(name);
11841192
if (NIL_P(idx)) return -1;
11851193
return struct_member_pos(s, idx);
@@ -1251,7 +1259,7 @@ invalid_struct_pos(VALUE s, VALUE idx)
12511259
VALUE
12521260
rb_struct_aref(VALUE s, VALUE idx)
12531261
{
1254-
int i = rb_struct_pos(s, &idx);
1262+
int i = rb_struct_pos(s, &idx, false);
12551263
if (i < 0) invalid_struct_pos(s, idx);
12561264
return RSTRUCT_GET_RAW(s, i);
12571265
}
@@ -1289,26 +1297,26 @@ rb_struct_aref(VALUE s, VALUE idx)
12891297
VALUE
12901298
rb_struct_aset(VALUE s, VALUE idx, VALUE val)
12911299
{
1292-
int i = rb_struct_pos(s, &idx);
1300+
int i = rb_struct_pos(s, &idx, false);
12931301
if (i < 0) invalid_struct_pos(s, idx);
12941302
rb_struct_modify(s);
12951303
RSTRUCT_SET_RAW(s, i, val);
12961304
return val;
12971305
}
12981306

12991307
FUNC_MINIMIZED(VALUE rb_struct_lookup(VALUE s, VALUE idx));
1300-
NOINLINE(static VALUE rb_struct_lookup_default(VALUE s, VALUE idx, VALUE notfound));
1308+
NOINLINE(static VALUE rb_struct_lookup_default(VALUE s, VALUE idx, VALUE notfound, bool name_only));
13011309

13021310
VALUE
13031311
rb_struct_lookup(VALUE s, VALUE idx)
13041312
{
1305-
return rb_struct_lookup_default(s, idx, Qnil);
1313+
return rb_struct_lookup_default(s, idx, Qnil, false);
13061314
}
13071315

13081316
static VALUE
1309-
rb_struct_lookup_default(VALUE s, VALUE idx, VALUE notfound)
1317+
rb_struct_lookup_default(VALUE s, VALUE idx, VALUE notfound, bool name_only)
13101318
{
1311-
int i = rb_struct_pos(s, &idx);
1319+
int i = rb_struct_pos(s, &idx, name_only);
13121320
if (i < 0) return notfound;
13131321
return RSTRUCT_GET_RAW(s, i);
13141322
}
@@ -1744,6 +1752,21 @@ rb_data_define(VALUE super, ...)
17441752
return klass;
17451753
}
17461754

1755+
static int
1756+
data_hash_set_i(VALUE key, VALUE val, VALUE arg)
1757+
{
1758+
struct struct_hash_set_arg *args = (struct struct_hash_set_arg *)arg;
1759+
int i = struct_hash_aset(key, val, args, true);
1760+
if (i >= 0 && args->missing_count > 0) {
1761+
VALUE k = RARRAY_AREF(args->missing_keywords, i);
1762+
if (!NIL_P(k)) {
1763+
RARRAY_ASET(args->missing_keywords, i, Qnil);
1764+
args->missing_count--;
1765+
}
1766+
}
1767+
return ST_CONTINUE;
1768+
}
1769+
17471770
/*
17481771
* call-seq:
17491772
* DataClass::members -> array_of_symbols
@@ -1827,24 +1850,31 @@ rb_data_initialize_m(int argc, const VALUE *argv, VALUE self)
18271850
rb_error_arity(argc, 0, 0);
18281851
}
18291852

1830-
struct struct_hash_set_arg arg;
1853+
VALUE missing = rb_ary_dup(members);
1854+
RBASIC_CLEAR_CLASS(missing);
1855+
struct struct_hash_set_arg arg = {
1856+
.self = self,
1857+
.unknown_keywords = Qnil,
1858+
.missing_keywords = missing,
1859+
.missing_count = (long)num_members,
1860+
};
18311861
rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self), num_members);
1832-
arg.self = self;
1833-
arg.unknown_keywords = Qnil;
1834-
arg.found_keywords = Qnil;
1835-
rb_hash_foreach(argv[0], struct_hash_set_i, (VALUE)&arg);
1862+
rb_hash_foreach(argv[0], data_hash_set_i, (VALUE)&arg);
18361863
// Freeze early before potentially raising, so that we don't leave an
18371864
// unfrozen copy on the heap, which could get exposed via ObjectSpace.
18381865
OBJ_FREEZE(self);
1839-
if (arg.found_keywords != Qnil) {
1840-
VALUE missing = rb_ary_diff(members, rb_hash_keys(arg.found_keywords));
1841-
if (RARRAY_LEN(missing) > 0) {
1842-
rb_exc_raise(rb_keyword_error_new("missing", missing));
1843-
}
1866+
if (UNLIKELY(arg.missing_count > 0)) {
1867+
rb_ary_compact_bang(missing);
1868+
RUBY_ASSERT(RARRAY_LEN(missing) == arg.missing_count, "missing_count=%ld but %ld", arg.missing_count, RARRAY_LEN(missing));
1869+
RBASIC_SET_CLASS_RAW(missing, rb_cArray);
1870+
rb_exc_raise(rb_keyword_error_new("missing", missing));
18441871
}
1845-
if (arg.unknown_keywords != Qnil) {
1846-
rb_exc_raise(rb_keyword_error_new("unknown", arg.unknown_keywords));
1872+
VALUE unknown_keywords = arg.unknown_keywords;
1873+
if (UNLIKELY(!NIL_P(unknown_keywords))) {
1874+
RBASIC_SET_CLASS_RAW(unknown_keywords, rb_cArray);
1875+
rb_exc_raise(rb_keyword_error_new("unknown", unknown_keywords));
18471876
}
1877+
18481878
return Qnil;
18491879
}
18501880

@@ -2099,7 +2129,11 @@ rb_data_inspect(VALUE s)
20992129
* end
21002130
*/
21012131

2102-
#define rb_data_deconstruct_keys rb_struct_deconstruct_keys
2132+
static VALUE
2133+
rb_data_deconstruct_keys(VALUE s, VALUE keys)
2134+
{
2135+
return deconstruct_keys(s, keys, true);
2136+
}
21032137

21042138
/*
21052139
* Document-class: Struct

0 commit comments

Comments
 (0)