From a37ec32968bb30c86cc50a152e9b3a742ea92015 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 23 Oct 2019 18:14:47 -0400 Subject: [PATCH 1/2] Implement MatchData#{allocate,initialize_copy} so #dup and #clone can work --- spec/ruby/core/matchdata/dup_spec.rb | 14 +++++++++ .../core/regexp/MatchDataLayout.java | 6 +++- .../core/regexp/MatchDataNodes.java | 29 +++++++++++++++++-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 spec/ruby/core/matchdata/dup_spec.rb diff --git a/spec/ruby/core/matchdata/dup_spec.rb b/spec/ruby/core/matchdata/dup_spec.rb new file mode 100644 index 000000000000..70877f07ebca --- /dev/null +++ b/spec/ruby/core/matchdata/dup_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe "MatchData#dup" do + it "duplicates the match data" do + original = /ll/.match("hello") + original.instance_variable_set(:@custom_ivar, 42) + duplicate = original.dup + + duplicate.instance_variable_get(:@custom_ivar).should == 42 + original.regexp.should == duplicate.regexp + original.string.should == duplicate.string + original.offset(0).should == duplicate.offset(0) + end +end diff --git a/src/main/java/org/truffleruby/core/regexp/MatchDataLayout.java b/src/main/java/org/truffleruby/core/regexp/MatchDataLayout.java index 944dc5bee8e8..eb5093411ae9 100644 --- a/src/main/java/org/truffleruby/core/regexp/MatchDataLayout.java +++ b/src/main/java/org/truffleruby/core/regexp/MatchDataLayout.java @@ -23,7 +23,7 @@ public interface MatchDataLayout extends BasicObjectLayout { DynamicObjectFactory createMatchDataShape(DynamicObject logicalClass, DynamicObject metaClass); - Object[] build(DynamicObject source, DynamicObject regexp, Region region, @Nullable Region charOffsets); + Object[] build(@Nullable DynamicObject source, @Nullable DynamicObject regexp, @Nullable Region region, @Nullable Region charOffsets); boolean isMatchData(DynamicObject object); @@ -31,6 +31,8 @@ DynamicObjectFactory createMatchDataShape(DynamicObject logicalClass, DynamicObject getSource(DynamicObject object); + void setSource(DynamicObject object, DynamicObject value); + /** Either a Regexp or a String for the case of String#gsub(String) */ DynamicObject getRegexp(DynamicObject object); @@ -38,6 +40,8 @@ DynamicObjectFactory createMatchDataShape(DynamicObject logicalClass, Region getRegion(DynamicObject object); + void setRegion(DynamicObject object, Region value); + Region getCharOffsets(DynamicObject object); void setCharOffsets(DynamicObject object, Region value); diff --git a/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java b/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java index de4b0361d436..014eb9688b74 100644 --- a/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java +++ b/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java @@ -628,15 +628,38 @@ protected DynamicObject regexp(DynamicObject matchData, @CoreMethod(names = "__allocate__", constructor = true, visibility = Visibility.PRIVATE) public abstract static class AllocateNode extends UnaryCoreMethodNode { - // MatchData can be allocated in MRI but it does not seem to be any useful @TruffleBoundary @Specialization - protected DynamicObject allocate(DynamicObject rubyClass) { - throw new RaiseException(getContext(), coreExceptions().typeErrorAllocatorUndefinedFor(rubyClass, this)); + protected DynamicObject allocate(DynamicObject rubyClass, @Cached AllocateObjectNode allocateNode) { + return allocateNode.allocate(rubyClass, Layouts.MATCH_DATA.build(null, null, null, null)); } } + @CoreMethod(names = "initialize_copy", required = 1, raiseIfFrozenSelf = true) + public abstract static class InitializeCopyNode extends CoreMethodArrayArgumentsNode { + + @Specialization + protected DynamicObject initializeCopy(DynamicObject self, DynamicObject from) { + if (self == from) { + return self; + } + + if (!Layouts.MATCH_DATA.isMatchData(from)) { + throw new RaiseException( + getContext(), + coreExceptions().typeError("initialize_copy should take same class object", this)); + } + + + Layouts.MATCH_DATA.setSource(self, Layouts.MATCH_DATA.getSource(from)); + Layouts.MATCH_DATA.setRegexp(self, Layouts.MATCH_DATA.getRegexp(from)); + Layouts.MATCH_DATA.setRegion(self, Layouts.MATCH_DATA.getRegion(from)); + Layouts.MATCH_DATA.setCharOffsets(self, Layouts.MATCH_DATA.getCharOffsets(from)); + return self; + } + } + @Primitive(name = "match_data_get_source") public abstract static class GetSourceNode extends PrimitiveArrayArgumentsNode { From 50d828277bc437e80f1153ed4c234caac6978011 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 24 Oct 2019 16:32:47 -0400 Subject: [PATCH 2/2] Guard against uninitialized MatchData in methods --- spec/ruby/core/matchdata/begin_spec.rb | 7 ++ spec/ruby/core/matchdata/captures_spec.rb | 5 + .../core/matchdata/element_reference_spec.rb | 5 + spec/ruby/core/matchdata/end_spec.rb | 7 ++ spec/ruby/core/matchdata/inspect_spec.rb | 5 + .../core/matchdata/named_captures_spec.rb | 5 + spec/ruby/core/matchdata/names_spec.rb | 5 + spec/ruby/core/matchdata/offset_spec.rb | 5 + spec/ruby/core/matchdata/post_match_spec.rb | 5 + spec/ruby/core/matchdata/pre_match_spec.rb | 5 + spec/ruby/core/matchdata/regexp_spec.rb | 5 + spec/ruby/core/matchdata/shared/eql.rb | 11 ++ spec/ruby/core/matchdata/shared/length.rb | 5 + spec/ruby/core/matchdata/string_spec.rb | 5 + spec/ruby/core/matchdata/to_a_spec.rb | 5 + spec/ruby/core/matchdata/to_s_spec.rb | 5 + spec/ruby/core/matchdata/values_at_spec.rb | 5 + .../core/regexp/MatchDataGuards.java | 20 +++ .../core/regexp/MatchDataNodes.java | 114 +++++++++++++++--- src/main/ruby/truffleruby/core/regexp.rb | 21 ++++ 20 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/truffleruby/core/regexp/MatchDataGuards.java diff --git a/spec/ruby/core/matchdata/begin_spec.rb b/spec/ruby/core/matchdata/begin_spec.rb index 85c454da562d..758bb8500aee 100644 --- a/spec/ruby/core/matchdata/begin_spec.rb +++ b/spec/ruby/core/matchdata/begin_spec.rb @@ -101,4 +101,11 @@ match_data.begin(:æ).should == 1 end end + + context "when uninitialized" do + it "raises TypeError" do + match_data = MatchData.allocate + -> { match_data.begin(0) }.should raise_error(TypeError) + end + end end diff --git a/spec/ruby/core/matchdata/captures_spec.rb b/spec/ruby/core/matchdata/captures_spec.rb index 8c0d2978b717..77f72858a473 100644 --- a/spec/ruby/core/matchdata/captures_spec.rb +++ b/spec/ruby/core/matchdata/captures_spec.rb @@ -1,6 +1,11 @@ require_relative '../../spec_helper' describe "MatchData#captures" do + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.captures }.should raise_error(TypeError) + end + it "returns an array of the match captures" do /(.)(.)(\d+)(\d)/.match("THX1138.").captures.should == ["H","X","113","8"] end diff --git a/spec/ruby/core/matchdata/element_reference_spec.rb b/spec/ruby/core/matchdata/element_reference_spec.rb index 26550ac94d0b..d729c8fc1d00 100644 --- a/spec/ruby/core/matchdata/element_reference_spec.rb +++ b/spec/ruby/core/matchdata/element_reference_spec.rb @@ -1,6 +1,11 @@ require_relative '../../spec_helper' describe "MatchData#[]" do + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data[1] }.should raise_error(TypeError) + end + it "acts as normal array indexing [index]" do md = /(.)(.)(\d+)(\d)/.match("THX1138.") diff --git a/spec/ruby/core/matchdata/end_spec.rb b/spec/ruby/core/matchdata/end_spec.rb index d01b0a8b304f..4c7d6bd85ad9 100644 --- a/spec/ruby/core/matchdata/end_spec.rb +++ b/spec/ruby/core/matchdata/end_spec.rb @@ -101,4 +101,11 @@ match_data.end(:æ).should == 2 end end + + context "when uninitialized" do + it "raises TypeError" do + match_data = MatchData.allocate + -> { match_data.end(0) }.should raise_error(TypeError) + end + end end diff --git a/spec/ruby/core/matchdata/inspect_spec.rb b/spec/ruby/core/matchdata/inspect_spec.rb index 531525767705..165bc0c76c4a 100644 --- a/spec/ruby/core/matchdata/inspect_spec.rb +++ b/spec/ruby/core/matchdata/inspect_spec.rb @@ -5,6 +5,11 @@ @match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") end + it "returns a String when receiver is uninitialized" do + match_data = MatchData.allocate + match_data.inspect.should be_kind_of(String) + end + it "returns a String" do @match_data.inspect.should be_kind_of(String) end diff --git a/spec/ruby/core/matchdata/named_captures_spec.rb b/spec/ruby/core/matchdata/named_captures_spec.rb index 9b1e324a2480..244969f24584 100644 --- a/spec/ruby/core/matchdata/named_captures_spec.rb +++ b/spec/ruby/core/matchdata/named_captures_spec.rb @@ -12,4 +12,9 @@ it 'returns the latest matched capture, even if a later one that does not match exists' do /\A(?.)(?.)(?.)(?.)?\z/.match('012').named_captures.should == { 'a' => '0', 'b' => '2' } end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.named_captures }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/names_spec.rb b/spec/ruby/core/matchdata/names_spec.rb index 25ca06ced9fe..49141375a3dd 100644 --- a/spec/ruby/core/matchdata/names_spec.rb +++ b/spec/ruby/core/matchdata/names_spec.rb @@ -30,4 +30,9 @@ r = /(?hay)(?.)(?tack)/ 'haystack'.match(r).names.should == r.names end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.names }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/offset_spec.rb b/spec/ruby/core/matchdata/offset_spec.rb index 1ccb54b7a7f2..e1d5c8ec7104 100644 --- a/spec/ruby/core/matchdata/offset_spec.rb +++ b/spec/ruby/core/matchdata/offset_spec.rb @@ -27,4 +27,9 @@ match_data.offset(4).should == [6, 7] end end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.offset(0) }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/post_match_spec.rb b/spec/ruby/core/matchdata/post_match_spec.rb index 6e1343812468..88cf303cfdc4 100644 --- a/spec/ruby/core/matchdata/post_match_spec.rb +++ b/spec/ruby/core/matchdata/post_match_spec.rb @@ -31,4 +31,9 @@ str = "abc".force_encoding Encoding::ISO_8859_1 str.match(/c/).post_match.encoding.should equal(Encoding::ISO_8859_1) end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.post_match }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/pre_match_spec.rb b/spec/ruby/core/matchdata/pre_match_spec.rb index 816cc91eb2fc..5cb34b28a7ac 100644 --- a/spec/ruby/core/matchdata/pre_match_spec.rb +++ b/spec/ruby/core/matchdata/pre_match_spec.rb @@ -31,4 +31,9 @@ str = "abc".force_encoding Encoding::ISO_8859_1 str.match(/a/).pre_match.encoding.should equal(Encoding::ISO_8859_1) end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.pre_match }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/regexp_spec.rb b/spec/ruby/core/matchdata/regexp_spec.rb index 7a4783434c62..ed05435cfa6b 100644 --- a/spec/ruby/core/matchdata/regexp_spec.rb +++ b/spec/ruby/core/matchdata/regexp_spec.rb @@ -15,4 +15,9 @@ 'he[[o'.gsub('[', ']') $~.regexp.should == /\[/ end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.regexp }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/shared/eql.rb b/spec/ruby/core/matchdata/shared/eql.rb index e021baa1784c..92acd0412d09 100644 --- a/spec/ruby/core/matchdata/shared/eql.rb +++ b/spec/ruby/core/matchdata/shared/eql.rb @@ -23,4 +23,15 @@ a = 'haystack'.match(/hay/) a.send(@method, Object.new).should be_false end + + it "returns false if arguments are different and both are uninitialized" do + a = MatchData.allocate + b = MatchData.allocate + a.send(@method, b).should be_false + end + + it "returns true if arguments are the identical and uninitialized" do + match_data = MatchData.allocate + match_data.send(@method, match_data).should be_true + end end diff --git a/spec/ruby/core/matchdata/shared/length.rb b/spec/ruby/core/matchdata/shared/length.rb index 6312a7ed4ccc..3df925666380 100644 --- a/spec/ruby/core/matchdata/shared/length.rb +++ b/spec/ruby/core/matchdata/shared/length.rb @@ -2,4 +2,9 @@ it "length should return the number of elements in the match array" do /(.)(.)(\d+)(\d)/.match("THX1138.").send(@method).should == 5 end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.send(@method) }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/string_spec.rb b/spec/ruby/core/matchdata/string_spec.rb index db0d5dfbdca3..d1f3ad97b193 100644 --- a/spec/ruby/core/matchdata/string_spec.rb +++ b/spec/ruby/core/matchdata/string_spec.rb @@ -22,4 +22,9 @@ $~.string.should == 'he[[o' $~.string.frozen?.should == true end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.string }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/to_a_spec.rb b/spec/ruby/core/matchdata/to_a_spec.rb index 6231d096fbd8..ddf99d39e43b 100644 --- a/spec/ruby/core/matchdata/to_a_spec.rb +++ b/spec/ruby/core/matchdata/to_a_spec.rb @@ -4,4 +4,9 @@ it "returns an array of matches" do /(.)(.)(\d+)(\d)/.match("THX1138.").to_a.should == ["HX1138", "H", "X", "113", "8"] end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.to_a }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/to_s_spec.rb b/spec/ruby/core/matchdata/to_s_spec.rb index 9e213bb342d0..a0c1afb579b7 100644 --- a/spec/ruby/core/matchdata/to_s_spec.rb +++ b/spec/ruby/core/matchdata/to_s_spec.rb @@ -4,4 +4,9 @@ it "returns the entire matched string" do /(.)(.)(\d+)(\d)/.match("THX1138.").to_s.should == "HX1138" end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.to_s }.should raise_error(TypeError) + end end diff --git a/spec/ruby/core/matchdata/values_at_spec.rb b/spec/ruby/core/matchdata/values_at_spec.rb index 8f7fdf557cb5..7575cfde4868 100644 --- a/spec/ruby/core/matchdata/values_at_spec.rb +++ b/spec/ruby/core/matchdata/values_at_spec.rb @@ -18,4 +18,9 @@ it 'takes names and indices' do /\A(?.)(?.)\z/.match('01').values_at(0, 1, 2, :a, :b).should == ['01', '0', '1', '0', '1'] end + + it "raises TypeError when uninitialized" do + match_data = MatchData.allocate + -> { match_data.values_at(0) }.should raise_error(TypeError) + end end diff --git a/src/main/java/org/truffleruby/core/regexp/MatchDataGuards.java b/src/main/java/org/truffleruby/core/regexp/MatchDataGuards.java new file mode 100644 index 000000000000..54ea11551253 --- /dev/null +++ b/src/main/java/org/truffleruby/core/regexp/MatchDataGuards.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. This + * code is released under a tri EPL/GPL/LGPL license. You can use it, + * redistribute it and/or modify it under the terms of the: + * + * Eclipse Public License version 2.0, or + * GNU General Public License version 2, or + * GNU Lesser General Public License version 2.1. + */ + +package org.truffleruby.core.regexp; + +import com.oracle.truffle.api.object.DynamicObject; +import org.truffleruby.Layouts; + +public class MatchDataGuards { + public static boolean isInitialized(DynamicObject matchData) { + return Layouts.MATCH_DATA.getRegexp(matchData) != null; + } +} diff --git a/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java b/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java index 014eb9688b74..21e0a4648cd4 100644 --- a/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java +++ b/src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java @@ -48,6 +48,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.profiles.BranchProfile; @@ -56,6 +57,8 @@ @CoreModule(value = "MatchData", isClass = true) public abstract class MatchDataNodes { + static final String UNINITIALIZED_MESSAGE = "uninitialized Match"; + @TruffleBoundary public static Object begin(RubyContext context, DynamicObject matchData, int index) { // Taken from org.jruby.RubyMatchData @@ -201,6 +204,7 @@ protected Object create(DynamicObject regexp, DynamicObject string, DynamicObjec lowerFixnum = { 1, 2 }, taintFrom = 0, argumentNames = { "index_start_range_or_name", "length" }) + @ImportStatic(MatchDataGuards.class) public abstract static class GetIndexNode extends CoreMethodArrayArgumentsNode { @Child private ToIntNode toIntNode; @@ -215,7 +219,7 @@ public static GetIndexNode create(RubyNode... nodes) { public abstract Object executeGetIndex(Object matchData, Object index, Object length); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected Object getIndex(DynamicObject matchData, int index, NotProvided length, @Cached("createBinaryProfile()") ConditionProfile normalizedIndexProfile, @Cached("createBinaryProfile()") ConditionProfile indexOutOfBoundsProfile, @@ -242,7 +246,7 @@ protected Object getIndex(DynamicObject matchData, int index, NotProvided length } } - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected Object getIndex(DynamicObject matchData, int index, int length) { // TODO BJF 15-May-2015 Need to handle negative indexes and lengths and out of bounds final Object[] values = getValuesNode.execute(matchData); @@ -253,6 +257,7 @@ protected Object getIndex(DynamicObject matchData, int index, int length) { @Specialization( guards = { + "isInitialized(matchData)", "isRubySymbol(cachedIndex)", "name != null", "getRegexp(matchData) == regexp", @@ -272,18 +277,18 @@ protected Object getIndexSymbolSingleMatch(DynamicObject matchData, DynamicObjec } } - @Specialization(guards = "isRubySymbol(index)") + @Specialization(guards = { "isInitialized(matchData)", "isRubySymbol(index)" }) protected Object getIndexSymbol(DynamicObject matchData, DynamicObject index, NotProvided length, @Cached BranchProfile errorProfile) { return executeGetIndex(matchData, getBackRefFromSymbol(matchData, index), NotProvided.INSTANCE); } - @Specialization(guards = "isRubyString(index)") + @Specialization(guards = { "isInitialized(matchData)", "isRubyString(index)" }) protected Object getIndexString(DynamicObject matchData, DynamicObject index, NotProvided length) { return executeGetIndex(matchData, getBackRefFromString(matchData, index), NotProvided.INSTANCE); } - @Specialization(guards = { "!isRubySymbol(index)", "!isRubyString(index)", "!isIntRange(index)" }) + @Specialization(guards = { "isInitialized(matchData)", "!isRubySymbol(index)", "!isRubyString(index)", "!isIntRange(index)" }) protected Object getIndex(DynamicObject matchData, Object index, NotProvided length) { if (toIntNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -294,7 +299,7 @@ protected Object getIndex(DynamicObject matchData, Object index, NotProvided len } @TruffleBoundary - @Specialization(guards = "isIntRange(range)") + @Specialization(guards = { "isInitialized(matchData)", "isIntRange(range)"}) protected Object getIndex(DynamicObject matchData, DynamicObject range, NotProvided len) { final Object[] values = getValuesNode.execute(matchData); final int normalizedIndex = ArrayOperations @@ -308,6 +313,13 @@ protected Object getIndex(DynamicObject matchData, DynamicObject range, NotProvi return createArray(store, length); } + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData, Object index, NotProvided length) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } + @TruffleBoundary protected static NameEntry findNameEntry(DynamicObject regexp, DynamicObject string) { Regex regex = Layouts.REGEXP.getRegex(regexp); @@ -454,16 +466,24 @@ protected Object[] getValuesSlow(DynamicObject matchData) { } @CoreMethod(names = "captures") + @ImportStatic(MatchDataGuards.class) public abstract static class CapturesNode extends CoreMethodArrayArgumentsNode { @Child private ValuesNode valuesNode = ValuesNode.create(); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected DynamicObject toA(VirtualFrame frame, DynamicObject matchData) { Object[] objects = getCaptures(valuesNode.execute(matchData)); return createArray(objects, objects.length); } + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } + private static Object[] getCaptures(Object[] values) { return ArrayUtils.extractRange(values, 1, values.length); } @@ -492,9 +512,10 @@ protected boolean inBounds(DynamicObject matchData, int index) { @NonStandard @CoreMethod(names = "byte_begin", required = 1, lowerFixnum = 1) + @ImportStatic(MatchDataGuards.class) public abstract static class ByteBeginNode extends CoreMethodArrayArgumentsNode { - @Specialization(guards = "inBounds(matchData, index)") + @Specialization(guards = { "isInitialized(matchData)", "inBounds(matchData, index)" }) protected Object byteBegin(DynamicObject matchData, int index) { int b = Layouts.MATCH_DATA.getRegion(matchData).beg[index]; if (b < 0) { @@ -504,6 +525,13 @@ protected Object byteBegin(DynamicObject matchData, int index) { } } + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData, int index) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } + protected boolean inBounds(DynamicObject matchData, int index) { return index >= 0 && index < Layouts.MATCH_DATA.getRegion(matchData).numRegs; } @@ -511,9 +539,10 @@ protected boolean inBounds(DynamicObject matchData, int index) { @NonStandard @CoreMethod(names = "byte_end", required = 1, lowerFixnum = 1) + @ImportStatic(MatchDataGuards.class) public abstract static class ByteEndNode extends CoreMethodArrayArgumentsNode { - @Specialization(guards = "inBounds(matchData, index)") + @Specialization(guards = { "isInitialized(matchData)", "inBounds(matchData, index)" }) protected Object byteEnd(DynamicObject matchData, int index) { int e = Layouts.MATCH_DATA.getRegion(matchData).end[index]; if (e < 0) { @@ -523,24 +552,39 @@ protected Object byteEnd(DynamicObject matchData, int index) { } } + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData, int index) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } + protected boolean inBounds(DynamicObject matchData, int index) { return index >= 0 && index < Layouts.MATCH_DATA.getRegion(matchData).numRegs; } } @CoreMethod(names = { "length", "size" }) + @ImportStatic(MatchDataGuards.class) public abstract static class LengthNode extends CoreMethodArrayArgumentsNode { @Child private ValuesNode getValues = ValuesNode.create(); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected int length(DynamicObject matchData) { return getValues.execute(matchData).length; } + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } } @CoreMethod(names = "pre_match", taintFrom = 0) + @ImportStatic(MatchDataGuards.class) public abstract static class PreMatchNode extends CoreMethodArrayArgumentsNode { @Child private RopeNodes.SubstringNode substringNode = RopeNodes.SubstringNode.create(); @@ -548,7 +592,7 @@ public abstract static class PreMatchNode extends CoreMethodArrayArgumentsNode { public abstract DynamicObject execute(DynamicObject matchData); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected Object preMatch(DynamicObject matchData) { DynamicObject source = Layouts.MATCH_DATA.getSource(matchData); Rope sourceRope = StringOperations.rope(source); @@ -560,9 +604,17 @@ protected Object preMatch(DynamicObject matchData) { .allocate(Layouts.BASIC_OBJECT.getLogicalClass(source), Layouts.STRING.build(false, false, rope)); return string; } + + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } } @CoreMethod(names = "post_match", taintFrom = 0) + @ImportStatic(MatchDataGuards.class) public abstract static class PostMatchNode extends CoreMethodArrayArgumentsNode { @Child private RopeNodes.SubstringNode substringNode = RopeNodes.SubstringNode.create(); @@ -570,7 +622,7 @@ public abstract static class PostMatchNode extends CoreMethodArrayArgumentsNode public abstract DynamicObject execute(DynamicObject matchData); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected Object postMatch(DynamicObject matchData) { DynamicObject source = Layouts.MATCH_DATA.getSource(matchData); Rope sourceRope = StringOperations.rope(source); @@ -582,21 +634,37 @@ protected Object postMatch(DynamicObject matchData) { .allocate(Layouts.BASIC_OBJECT.getLogicalClass(source), Layouts.STRING.build(false, false, rope)); return string; } + + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } } @CoreMethod(names = "to_a") + @ImportStatic(MatchDataGuards.class) public abstract static class ToANode extends CoreMethodArrayArgumentsNode { @Child ValuesNode valuesNode = ValuesNode.create(); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected DynamicObject toA(DynamicObject matchData) { Object[] objects = ArrayUtils.copy(valuesNode.execute(matchData)); return createArray(objects, objects.length); } + + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } } @CoreMethod(names = "regexp") + @ImportStatic(MatchDataGuards.class) public abstract static class RegexpNode extends CoreMethodArrayArgumentsNode { public static RegexpNode create() { @@ -605,7 +673,7 @@ public static RegexpNode create() { public abstract DynamicObject executeGetRegexp(DynamicObject matchData); - @Specialization + @Specialization(guards = "isInitialized(matchData)") protected DynamicObject regexp(DynamicObject matchData, @Cached("createBinaryProfile()") ConditionProfile profile, @Cached("createPrivate()") CallDispatchHeadNode stringToRegexp) { @@ -623,6 +691,13 @@ protected DynamicObject regexp(DynamicObject matchData, } } + @Specialization(guards = "!isInitialized(matchData)") + protected DynamicObject uninitialized(DynamicObject matchData) { + throw new RaiseException( + getContext(), + coreExceptions().typeError(MatchDataNodes.UNINITIALIZED_MESSAGE, this)); + } + } @CoreMethod(names = "__allocate__", constructor = true, visibility = Visibility.PRIVATE) @@ -645,7 +720,7 @@ protected DynamicObject initializeCopy(DynamicObject self, DynamicObject from) { return self; } - if (!Layouts.MATCH_DATA.isMatchData(from)) { + if (Layouts.BASIC_OBJECT.getLogicalClass(self) != Layouts.BASIC_OBJECT.getLogicalClass(from)) { throw new RaiseException( getContext(), coreExceptions().typeError("initialize_copy should take same class object", this)); @@ -669,6 +744,15 @@ protected DynamicObject getSource(DynamicObject matchData) { } } + @Primitive(name = "match_data_initialized?") + public abstract static class InitializedNode extends PrimitiveArrayArgumentsNode { + + @Specialization + protected boolean isInitialized(DynamicObject matchData) { + return MatchDataGuards.isInitialized(matchData); + } + } + public static final class Pair implements Comparable { int bytePos, charPos; diff --git a/src/main/ruby/truffleruby/core/regexp.rb b/src/main/ruby/truffleruby/core/regexp.rb index eee03aa344e6..3cd94a59e94e 100644 --- a/src/main/ruby/truffleruby/core/regexp.rb +++ b/src/main/ruby/truffleruby/core/regexp.rb @@ -321,6 +321,7 @@ def names class MatchData def offset(idx) + check_initialized out = [] out << self.begin(idx) out << self.end(idx) @@ -328,7 +329,11 @@ def offset(idx) end def ==(other) + return true if equal?(other) + other.kind_of?(MatchData) && + TrufflePrimitive.match_data_initialized?(self) && + TrufflePrimitive.match_data_initialized?(other) && string == other.string && regexp == other.regexp && captures == other.captures @@ -336,18 +341,22 @@ def ==(other) alias_method :eql?, :== def string + check_initialized TrufflePrimitive.match_data_get_source(self).dup.freeze end def names + check_initialized regexp.names end def named_captures + check_initialized names.collect { |name| [name, self[name]] }.to_h end def pre_match_from(idx) + check_initialized source = TrufflePrimitive.match_data_get_source(self) return source.byteslice(0, 0) if self.byte_begin(0) == 0 nd = self.byte_begin(0) - 1 @@ -355,6 +364,7 @@ def pre_match_from(idx) end def begin(index) + check_initialized backref = if String === index || Symbol === index names_to_backref = Hash[TrufflePrimitive.regexp_names(self.regexp)] names_to_backref[index.to_sym].last @@ -367,6 +377,7 @@ def begin(index) end def end(index) + check_initialized backref = if String === index || Symbol === index names_to_backref = Hash[TrufflePrimitive.regexp_names(self.regexp)] names_to_backref[index.to_sym].last @@ -379,10 +390,12 @@ def end(index) end def collapsing? + check_initialized self.byte_begin(0) == self.byte_end(0) end def inspect + return super unless TrufflePrimitive.match_data_initialized?(self) str = "#