Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a mutable java.lang.String

It's common knowledge that Java Strings are immutable. Immutable Strings are great addition to java since its inception. Immutability allows fast access and a lot of optimizations, significantly less error-prone compared to C-style strings, and helps enforce the security model.

It's possible to create a mutable one without using hacks, namely

  • java.lang.reflect
  • sun.misc.Unsafe
  • Classes in bootstrap classloader
  • JNI (or JNA as it requires JNI)

But is it possible in just plain Java, so that the string can be modified at any time? The question is How?

like image 224
bestsss Avatar asked Jun 21 '12 20:06

bestsss


People also ask

How do you make a String mutable in Java?

How to create a mutable string in java? To create a mutable string, we can use StringBuffer and StringBuilder class. Both classes create a mutable object of string but which one we should use is totally depends on the scenario.

Is Java Lang String mutable?

StringBuffer and StringBuilder are mutable versions of String in java, whereas the java String class is immutable. Immutable objects are those objects whose contents cannot be modified once created. Whenever an immutable object's content is changed, there will be a creation of a new object.

Where are mutable strings created in Java?

The Java String is immutable which means it cannot be changed. Whenever we change any string, a new instance is created. For mutable strings, you can use StringBuffer and StringBuilder classes.


2 Answers

Creating a java.lang.String with the Charset constructor, one can inject your own Charset, which brings your own CharsetDecoder. The CharsetDecoder gets a reference to a CharBuffer object in the decodeLoop method. The CharBuffer wraps the char[] of the original String object. Since the CharsetDecoder has a reference to it, you can change the underlying char[] using the CharBuffer, thus you have a mutable String.

public class MutableStringTest {       // http://stackoverflow.com/questions/11146255/how-to-create-mutable-java-lang-string#11146288     @Test     public void testMutableString() throws Exception {         final String s = createModifiableString();         System.out.println(s);         modify(s);         System.out.println(s);     }      private final AtomicReference<CharBuffer> cbRef = new AtomicReference<CharBuffer>();     private String createModifiableString() {         Charset charset = new Charset("foo", null) {             @Override             public boolean contains(Charset cs) {                 return false;             }              @Override             public CharsetDecoder newDecoder() {                 CharsetDecoder cd = new CharsetDecoder(this, 1.0f, 1.0f) {                     @Override                     protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {                         cbRef.set(out);                         while(in.remaining()>0) {                             out.append((char)in.get());                         }                         return CoderResult.UNDERFLOW;                     }                 };                 return cd;             }              @Override             public CharsetEncoder newEncoder() {                 return null;             }         };         return new String("abc".getBytes(), charset);     }     private void modify(String s) {         CharBuffer charBuffer = cbRef.get();         charBuffer.position(0);         charBuffer.put("xyz");     }  } 

Running the code prints

abc zzz 

I don't know how to correctly implement decodeLoop(), but i don't care right now :)

like image 193
mhaller Avatar answered Oct 13 '22 13:10

mhaller


The question received a good answer by @mhaller. I'd say the so-called-puzzle was pretty easy and by just looking at the available c-tors of String one should be able to find out the how part, a

Walkthrough

C-tor of interest is below, if you are to break-in/crack/look for security vulnerability always look for non-final arbitrary classes. The case here is java.nio.charset.Charset

 //String public String(byte bytes[], int offset, int length, Charset charset) {     if (charset == null)         throw new NullPointerException("charset");     checkBounds(bytes, offset, length);     char[] v = StringCoding.decode(charset, bytes, offset, length);     this.offset = 0;     this.count = v.length;     this.value = v; } 
The c-tor offers supposedly-fast way to convert byte[] to String by passing the Charset not the chartset name to avoid the lookup chartsetName->charset. It also allows passing an arbitrary Charset object to create String. Charset main routing converts the content of java.nio.ByteBuffer to CharBuffer. The CharBuffer may hold a reference to char[] and it's available via array(), also the CharBuffer is fully modifiable.
     //StringCoding     static char[] decode(Charset cs, byte[] ba, int off, int len) {         StringDecoder sd = new StringDecoder(cs, cs.name());         byte[] b = Arrays.copyOf(ba, ba.length);         return sd.decode(b, off, len);     }      //StringDecoder     char[] decode(byte[] ba, int off, int len) {         int en = scale(len, cd.maxCharsPerByte());         char[] ca = new char[en];         if (len == 0)             return ca;         cd.reset();         ByteBuffer bb = ByteBuffer.wrap(ba, off, len);         CharBuffer cb = CharBuffer.wrap(ca);         try {             CoderResult cr = cd.decode(bb, cb, true);             if (!cr.isUnderflow())                 cr.throwException();             cr = cd.flush(cb);             if (!cr.isUnderflow())                 cr.throwException();         } catch (CharacterCodingException x) {             // Substitution is always enabled,             // so this shouldn't happen             throw new Error(x);         }         return safeTrim(ca, cb.position(), cs);     } 

In order to prevent altering the char[] the java developers copy the array much like any other String construction (for instance public String(char value[])). However there is an exception - if no SecurityManager is installed, the char[] is not copied.

     //Trim the given char array to the given length     //     private static char[] safeTrim(char[] ca, int len, Charset cs) {         if (len == ca.length                  && (System.getSecurityManager() == null                 || cs.getClass().getClassLoader0() == null))             return ca;         else             return Arrays.copyOf(ca, len);     } 

So if there is no SecurityManager it's absolutely possible to have a modifiable CharBuffer/char[] that's being referenced by a String.

Everything looks fine by now - except the byte[] is also copied (the bold above). This is where java developers went lazy and massively wrong.

The copy is necessary to prevent the rogue Charset (example above) to be able alter the source byte[]. However, imagine the case of having around 512KB byte[] buffer that contains few String. Attempting to create a single small, few charts - new String(buf, position, position+32,charset) resulting in massive 512KB byte[] copy. If the buffer were 1KB or so, the impact will never be truly noticed. With large buffers, the performance hit is really huge, though. The simple fix would be to copy the relevant part.

...or well the designers of java.nio thought about by introducing read-only Buffers. Simply calling ByteBuffer.asReadOnlyBuffer() would have been enough (if the Charset.getClassLoader()!=null)* Sometimes even the guys working on java.lang can get it totally wrong.

*Class.getClassLoader() returns null for bootstrap classes, i.e. the ones coming with the JVM itself.

like image 35
Mechanical snail Avatar answered Oct 13 '22 13:10

Mechanical snail