It's common knowledge that Java String
s 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?
Simplier way to swap bootstrap class path of
java
andjavac
1) Go to jdk installation and copy to separate folder
rt.jar
andsrc.zip
2) Unpack String.java from sources zip and change it private field value of inner char array to public
3) Compile modified String.java with help of javac:
4) Move compiled String.class and other compiled classes to rt.jar in this directory
5) Create test class that use String private field
6) Create empty dir
target
and compile test class7) Run it
output is:
P.S this will only work with modified
rt.jar
and use this option to overridert.jar
is violation ofjre
licence.Don't reinvent the wheel. Apache commons provides just that.
I would say StringBuilder (or StringBuffer for multithreaded use). Yes at the end you get a immutable String. But that's the way to go.
For example the best way to append Strings in a loop is to use StringBuilder. Java itself uses StringBuilder when you use "fu " + variable + " ba".
http://docs.oracle.com/javase/6/docs/api/java/lang/StringBuilder.html
append(blub).append(5).appen("dfgdfg").toString();
Creating a
java.lang.String
with the Charset constructor, one can inject your own Charset, which brings your ownCharsetDecoder
. TheCharsetDecoder
gets a reference to aCharBuffer
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.Running the code prints
I don't know how to correctly implement decodeLoop(), but i don't care right now :)
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
The c-tor offers supposedly-fast way to convertjava.nio.charset.Charset
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 ofjava.nio.ByteBuffer
toCharBuffer
. The CharBuffer may hold a reference to char[] and it's available viaarray()
, also the CharBuffer is fully modifiable.In order to prevent altering the
char[]
the java developers copy the array much like any other String construction (for instancepublic String(char value[])
). However there is an exception - if no SecurityManager is installed, the char[] is not copied.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 callingByteBuffer.asReadOnlyBuffer()
would have been enough (if the Charset.getClassLoader()!=null)* Sometimes even the guys working onjava.lang
can get it totally wrong.*Class.getClassLoader() returns null for bootstrap classes, i.e. the ones coming with the JVM itself.