@@ -1046,6 +1046,24 @@ public final void modifyExpand(int length) {
10461046 clearCodeRange ();
10471047 }
10481048
1049+ /**
1050+ * Ensure the backing store belongs to this string and has enough space to add extraLength bytes.
1051+ *
1052+ * MRI: str_ensure_available_capa
1053+ *
1054+ * @param context the current thread context
1055+ * @param extraLength the extra length needed
1056+ */
1057+ public void ensureAvailable (ThreadContext context , int extraLength ) {
1058+ int realSize = value .getRealSize ();
1059+
1060+ if (realSize > Integer .MAX_VALUE - extraLength ) {
1061+ throw argumentError (context , "string sizes too big" );
1062+ }
1063+
1064+ modifyExpand (realSize + extraLength );
1065+ }
1066+
10491067 // io_set_read_length
10501068 public void setReadLength (int length ) {
10511069 if (size () != length ) {
@@ -5514,30 +5532,50 @@ private IRubyObject rpartitionMismatch(ThreadContext context) {
55145532
55155533 @ JRubyMethod (rest = true )
55165534 public IRubyObject append_as_bytes (ThreadContext context , IRubyObject [] args ) {
5517- checkFrozen ();
5518- modify ();
5535+ int neededCapacity = 0 ;
5536+ for (var arg : args ) neededCapacity += byteCapacityFor (context , arg );
5537+ this .ensureAvailable (context , neededCapacity );
5538+ for (var arg : args ) appendBytes (context , arg );
55195539
5520- int length = args .length ;
5521- for (int i = 0 ; i < length ; i ++) {
5522- var arg = args [i ];
5540+ return this ;
5541+ }
55235542
5524- if (arg instanceof RubyFixnum fix ) {
5525- cat (fix .getIntValue () & 0xff );
5526- } else if (arg instanceof RubyBignum big ) {
5527- cat (big .getIntValue () & 0xff );
5528- } else if (arg instanceof RubyString str ) {
5529- value .append (str .getByteList ());
5530- } else {
5531- throw typeError (context , str (context .runtime , "wrong argument type " , types (context .runtime , arg .getType ()), " (expected String or Integer)" ));
5532- }
5533- }
5543+ @ JRubyMethod
5544+ public IRubyObject append_as_bytes (ThreadContext context ) {
5545+ this .ensureAvailable (context , 0 );
55345546
5535- // FIXME: MRI tries and minimize pain by trying to figure out if it should change cr and this is a quick hammer.
5536- clearCodeRange ();
5547+ return this ;
5548+ }
5549+
5550+ @ JRubyMethod
5551+ public IRubyObject append_as_bytes (ThreadContext context , IRubyObject arg0 ) {
5552+ ensureAvailable (context , byteCapacityFor (context , arg0 ));
5553+ appendBytes (context , arg0 );
55375554
55385555 return this ;
55395556 }
55405557
5558+ private static int byteCapacityFor (ThreadContext context , IRubyObject arg ) {
5559+ return switch (arg ) {
5560+ case RubyInteger ignored -> 1 ;
5561+ case RubyString str -> str .getByteList ().realSize ();
5562+ default ->
5563+ throw typeError (context , str (context .runtime , "wrong argument type " , types (context .runtime , arg .getType ()), " (expected String or Integer)" ));
5564+ };
5565+ }
5566+
5567+ private void appendBytes (ThreadContext context , IRubyObject arg ) {
5568+ if (arg instanceof RubyFixnum fix ) {
5569+ cat (fix .getIntValue () & 0xff );
5570+ } else if (arg instanceof RubyBignum big ) {
5571+ cat (big .getBigIntegerValue ().intValue () & 0xff );
5572+ } else if (arg instanceof RubyString str ) {
5573+ value .append (str .getByteList ());
5574+ } else {
5575+ throw runtimeError (context , "BUG: append_as_bytes arguments should have been validated" );
5576+ }
5577+ }
5578+
55415579 /** rb_str_chop / rb_str_chop_bang
55425580 *
55435581 */
0 commit comments