diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb index 498d03581ac6..63d95f2fef2b 100644 --- a/spec/ruby/core/encoding/replicate_spec.rb +++ b/spec/ruby/core/encoding/replicate_spec.rb @@ -75,6 +75,13 @@ end end + ruby_version_is "3.2"..."3.3" do + it "raises EncodingError if too many encodings" do + code = '1_000.times {|i| Encoding::US_ASCII.replicate("R_#{i}") }' + ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') + end + end + ruby_version_is "3.3" do it "has been removed" do Encoding::US_ASCII.should_not.respond_to?(:replicate, true) diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index 36437cc7b683..93bf956996fb 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -2,7 +2,7 @@ require_relative 'spec_helper' require_relative 'fixtures/encoding' -load_extension('encoding') +extension_path = load_extension('encoding') describe :rb_enc_get_index, shared: true do it "returns the index of the encoding of a String" do @@ -721,4 +721,27 @@ str.bytes.should == [0, 0x24] end end + + describe "rb_define_dummy_encoding" do + it "defines the dummy encoding" do + @s.rb_define_dummy_encoding("FOO") + enc = Encoding.find("FOO") + enc.should.dummy? + end + + it "returns the index of the dummy encoding" do + index = @s.rb_define_dummy_encoding("BAR") + index.should == Encoding.list.size - 1 + end + + ruby_version_is "3.2" do + it "raises EncodingError if too many encodings" do + code = <<-RUBY + require #{extension_path.dump} + 1_000.times {|i| CApiEncodingSpecs.new.rb_define_dummy_encoding(\"R_\#{i}\") } + RUBY + ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') + end + end + end end diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c index 3343848b5429..aa8662cfbd64 100644 --- a/spec/ruby/optional/capi/ext/encoding_spec.c +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -320,6 +320,10 @@ static VALUE encoding_spec_rb_enc_left_char_head(VALUE self, VALUE str, VALUE of return LONG2NUM(result - ptr); } +static VALUE encoding_spec_rb_define_dummy_encoding(VALUE self, VALUE name) { + return INT2NUM(rb_define_dummy_encoding(RSTRING_PTR(name))); +} + void Init_encoding_spec(void) { VALUE cls; native_rb_encoding_pointer = (rb_encoding**) malloc(sizeof(rb_encoding*)); @@ -379,6 +383,7 @@ void Init_encoding_spec(void) { rb_define_method(cls, "rb_uv_to_utf8", encoding_spec_rb_uv_to_utf8, 2); rb_define_method(cls, "ONIGENC_MBC_CASE_FOLD", encoding_spec_ONIGENC_MBC_CASE_FOLD, 1); rb_define_method(cls, "rb_enc_left_char_head", encoding_spec_rb_enc_left_char_head, 2); + rb_define_method(cls, "rb_define_dummy_encoding", encoding_spec_rb_define_dummy_encoding, 1); } #ifdef __cplusplus diff --git a/spec/tags/core/encoding/replicate_tags.txt b/spec/tags/core/encoding/replicate_tags.txt new file mode 100644 index 000000000000..1772e1f5073d --- /dev/null +++ b/spec/tags/core/encoding/replicate_tags.txt @@ -0,0 +1 @@ +slow:Encoding#replicate raises EncodingError if too many encodings diff --git a/spec/tags/optional/capi/encoding_tags.txt b/spec/tags/optional/capi/encoding_tags.txt index f476def1e723..bca70e6a51be 100644 --- a/spec/tags/optional/capi/encoding_tags.txt +++ b/spec/tags/optional/capi/encoding_tags.txt @@ -5,3 +5,4 @@ fails:C-API Encoding function rb_enc_copy sets the encoding of a Regexp to that fails:C-API Encoding function rb_enc_get_index returns -1 for an object without an encoding fails:C-API Encoding function rb_enc_set_index raises an ArgumentError for a non-encoding capable object fails:C-API Encoding function ENCODING_SET raises an ArgumentError for a non-encoding capable object +slow:C-API Encoding function rb_define_dummy_encoding raises EncodingError if too many encodings diff --git a/src/main/java/org/truffleruby/core/encoding/EncodingManager.java b/src/main/java/org/truffleruby/core/encoding/EncodingManager.java index e6ea53173217..2066f88d73b3 100644 --- a/src/main/java/org/truffleruby/core/encoding/EncodingManager.java +++ b/src/main/java/org/truffleruby/core/encoding/EncodingManager.java @@ -206,6 +206,10 @@ public synchronized Object[] getEncodingList() { return ArrayUtils.copyOf(ENCODING_LIST_BY_ENCODING_INDEX, ENCODING_LIST_BY_ENCODING_INDEX.length); } + public int getNumberOfEncodings() { + return ENCODING_LIST_BY_ENCODING_INDEX.length; + } + @TruffleBoundary public RubyEncoding getRubyEncoding(String name) { final String normalizedName = name.toLowerCase(Locale.ENGLISH); diff --git a/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java b/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java index 96c65e261f8b..63931e48fa21 100644 --- a/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java +++ b/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java @@ -705,6 +705,12 @@ static RubyArray encodingReplicate(RubyEncoding object, Object nameObject, @TruffleBoundary private static RubyEncoding replicate(Node node, String name, RubyEncoding encoding) { + if (getContext(node).getEncodingManager().getNumberOfEncodings() >= Encodings.MAX_NUMBER_OF_ENCODINGS) { + throw new RaiseException( + getContext(node), + coreExceptions(node).encodingErrorTooManyEncodings(Encodings.MAX_NUMBER_OF_ENCODINGS, node)); + } + return getContext(node).getEncodingManager().replicateEncoding(encoding, name); } @@ -726,6 +732,12 @@ static RubyArray createDummyEncoding(Object nameObject, @TruffleBoundary private static RubyEncoding createDummy(Node node, String name) { + if (getContext(node).getEncodingManager().getNumberOfEncodings() >= Encodings.MAX_NUMBER_OF_ENCODINGS) { + throw new RaiseException( + getContext(node), + coreExceptions(node).encodingErrorTooManyEncodings(Encodings.MAX_NUMBER_OF_ENCODINGS, node)); + } + return getContext(node).getEncodingManager().createDummyEncoding(name); } diff --git a/src/main/java/org/truffleruby/core/encoding/Encodings.java b/src/main/java/org/truffleruby/core/encoding/Encodings.java index 265dce245139..d0f703fe6b59 100644 --- a/src/main/java/org/truffleruby/core/encoding/Encodings.java +++ b/src/main/java/org/truffleruby/core/encoding/Encodings.java @@ -37,6 +37,7 @@ public final class Encodings { public static final int INITIAL_NUMBER_OF_ENCODINGS = EncodingDB.getEncodings().size(); + public static final int MAX_NUMBER_OF_ENCODINGS = 256; public static final RubyEncoding US_ASCII = initializeUsAscii(); private static final RubyEncoding[] BUILT_IN_ENCODINGS = initializeRubyEncodings(); diff --git a/src/main/java/org/truffleruby/core/exception/CoreExceptions.java b/src/main/java/org/truffleruby/core/exception/CoreExceptions.java index 8deb0d268c55..4c6e07952a79 100644 --- a/src/main/java/org/truffleruby/core/exception/CoreExceptions.java +++ b/src/main/java/org/truffleruby/core/exception/CoreExceptions.java @@ -1138,6 +1138,15 @@ public RubyException encodingError(Object string, RubyEncoding encoding, Node cu return ExceptionOperations.createRubyException(context, exceptionClass, errorMessage, currentNode, null); } + @TruffleBoundary + public RubyException encodingErrorTooManyEncodings(int maxSize, Node currentNode) { + RubyClass exceptionClass = context.getCoreLibrary().encodingErrorClass; + String message = StringUtils.format("too many encoding (> %d)", maxSize); + RubyString errorMessage = StringOperations.createUTF8String(context, language, message); + + return ExceptionOperations.createRubyException(context, exceptionClass, errorMessage, currentNode, null); + } + @TruffleBoundary public RubyException encodingCompatibilityErrorIncompatible(RubyEncoding a, RubyEncoding b, Node currentNode) { return encodingCompatibilityError(