Why is String immutable?

F

Fred Kleinschmidt

Why String is immutable? Is there simple explanation I can't find it?
For the same reason Integer is immutable: that's how the author(s) decided
to do it.

You could also ask: Why isn't a StringBuffer immutable?
 
C

Chris Smith

Why String is immutable? Is there simple explanation I can't find it?

There are at least two reasons.

The first is a design principle. Immutable classes for basic types like
String make designs much more comprehensible. If you pass a String
around the application, you don't need to worry about who else has a
reference to it, who might modify it without your realizing the
possibility, and so forth. Especially in a heavily multithreaded
environment like Java, that's the only way that it's safe to share a
String throughout the application.

The second reason has to do with the Java security model. When a
security policy is installed, certain restrictions can be enforced on --
for example -- which files can be read, which hosts can be connected to
over the network, and so on. The immutable String class ensures that
security-sensitive APIs only have to check the file name or host name
once, and can then rely on it to stay the same. A mutable String class
would introduce a race condition where the application (in another
thread) could modify the file name after the security check, but before
the file is actually opened, and thus circumvent the security mechanism.

If you think about it, you'll notice that when seen from some
perspectives, the second is a specialization on the first reason.
 
M

Matt Humphrey

Why String is immutable? Is there simple explanation I can't find it?

I've seen a number of reasons, although I don't know for certain which of
these the original designers had these in mind:

1) To put Strings on par with Floats, Integers, etc as value objects
specified literally and which cannot be modified by method side-effect.
2) So strings can be easily shared (rather than copying content)
3) So strings can be interned (and tested for equality via ==)
4) They're inherently threadsafe
5) To let them work cleanly as keys in maps without the possibility of
changing
6) To simplify learning the object model
7) So their hashcodes do not have to be recomputed
8) StringBuffer is perfectly good at managing mutable Strings

And, of course, depending on your Security Manager, it is possible (but
never a good idea) to change a String's contents via reflection.

Some results from Google:

http://www.acooke.org/andrew/immutable.html

http://www.programmersheaven.com/2/FAQ-JAVA-String-Is-Immutable

http://en.wikipedia.org/wiki/Immutable_object

Matt Humphrey (e-mail address removed) http://www.iviz.com/
 
T

Tor Iver Wilhelmsen

Why String is immutable? Is there simple explanation I can't find it?

Safety (no changing under the covers), efficiency (substring is just a
pointer to the same char array with different indices), reliability as
a hash value. At least.
 
M

Matt Rose

The second reason has to do with the Java security model. When a
security policy is installed, certain restrictions can be enforced on --
for example -- which files can be read, which hosts can be connected to
over the network, and so on. The immutable String class ensures that
security-sensitive APIs only have to check the file name or host name
once, and can then rely on it to stay the same. A mutable String class
would introduce a race condition where the application (in another
thread) could modify the file name after the security check, but before
the file is actually opened, and thus circumvent the security mechanism.

Hi, I wholeheartedly agree with your first point of it being the only
sane way to implement Strings from a design point of view, but I'm less
sure about relying on this for security. The underlying char[] is still
writable if you try a bit harder. I expect the method below could be
forbidden with the right security policy (ReflectPermission
seems to be granted by default on my system) but I suspect you could
still access the field directly if you craft your own byte code?

import java.lang.reflect.Field;

public class StringImmutabilityTest {

public static void main(String[] args) throws Exception {
String fileNameToServe = "/ftp/readme";
char[] injection = "/etc/passwd".toCharArray();
Field f = fileNameToServe.getClass().getDeclaredField("value");
f.setAccessible(true);
char[] val = (char[]) f.get(fileNameToServe);
System.arraycopy(injection, 0, val, 0, injection.length);
System.out.println(fileNameToServe);
}
}

Of course, you're probably doomed the moment you allow untrusted code
into your VM anyway!

By the way, calling new String(String) on any untrusted Strings will
probably keep you a little safe from this.

Matt
 
M

Matt Rose

Matt said:
The second reason has to do with the Java security model. When a
security policy is installed, certain restrictions can be enforced on --
for example -- which files can be read, which hosts can be connected to
over the network, and so on. The immutable String class ensures that
security-sensitive APIs only have to check the file name or host name
once, and can then rely on it to stay the same. A mutable String class
would introduce a race condition where the application (in another
thread) could modify the file name after the security check, but before
the file is actually opened, and thus circumvent the security mechanism.

Hi, I wholeheartedly agree with your first point of it being the only
sane way to implement Strings from a design point of view, but I'm less
sure about relying on this for security. The underlying char[] is still
writable if you try a bit harder. I expect the method below could be
forbidden with the right security policy (ReflectPermission
seems to be granted by default on my system) but I suspect you could
still access the field directly if you craft your own byte code?

import java.lang.reflect.Field;

public class StringImmutabilityTest {

public static void main(String[] args) throws Exception {
String fileNameToServe = "/ftp/readme";
char[] injection = "/etc/passwd".toCharArray();
Field f = fileNameToServe.getClass().getDeclaredField("value");
f.setAccessible(true);
char[] val = (char[]) f.get(fileNameToServe);
System.arraycopy(injection, 0, val, 0, injection.length);
System.out.println(fileNameToServe);
}
}

Of course, you're probably doomed the moment you allow untrusted code
into your VM anyway!

By the way, calling new String(String) on any untrusted Strings will
probably keep you a little safe from this.

Matt

Hmm, I take it back about new String(String) helping. This contructor
only copies the char[] if original String had some wasted space, e.g.
it was the product of a subString().

You'd need to duplicate the char[] yourself and wrap that in a String
if you're worried about malicious people having references to your
Strings.

Matt
 
M

Mark Rafn

Why String is immutable? Is there simple explanation I can't find it?

Don't forget to ask the important further question "when I design a class,
should I make it immutable?". IMO, the idea of immutable data objects is far
underutilized.

Immutability does a lot of things that aren't obvious at first glance, and you
probably want more of your classes to have these properties:

- sets and maps behave consistently. Mutable objects can go in and
later not be findable because their equals() and hashCode() are
different.
- clarity of intent. if your object is immutable, you never have to worry
they'll muck with it when you pass it into someone else's code. Likewise,
when they return an immutable object, you don't have to ask yourself
whether you need to check for value changes or set listeners on properties
before actually using the object.
- thread safety.
 
C

Chris Smith

Matt Rose said:
Hi, I wholeheartedly agree with your first point of it being the only
sane way to implement Strings from a design point of view, but I'm less
sure about relying on this for security. The underlying char[] is still
writable if you try a bit harder. I expect the method below could be
forbidden with the right security policy (ReflectPermission
seems to be granted by default on my system) but I suspect you could
still access the field directly if you craft your own byte code?

Everything is granted by default. The point is that if you install a
security manager, you can control this stuff. There are some
permissions that can never be granted to security-sensitive code; but
you just don't grant those permissions. One of those is
ReflectPermission("suppressAccessChecks"), which covers the call to
setAccessible.
Of course, you're probably doomed the moment you allow untrusted code
into your VM anyway!

Not at all. That's what applets do, millions of times per day. If you
were right, then someone could really cause havoc around the world.
You're not right, though.
 
M

Matt Rose

Chris said:
Matt Rose said:
Hi, I wholeheartedly agree with your first point of it being the only
sane way to implement Strings from a design point of view, but I'm less
sure about relying on this for security. The underlying char[] is still
writable if you try a bit harder. I expect the method below could be
forbidden with the right security policy (ReflectPermission
seems to be granted by default on my system) but I suspect you could
still access the field directly if you craft your own byte code?

Everything is granted by default. The point is that if you install a
security manager, you can control this stuff. There are some
permissions that can never be granted to security-sensitive code; but
you just don't grant those permissions. One of those is
ReflectPermission("suppressAccessChecks"), which covers the call to
setAccessible.

Does this get checked by the GETFIELD opcode? I can't find any
indication that it does in the VM spec but I haven't tested it. I never
normally get involved with bytecode.
Not at all. That's what applets do, millions of times per day. If you
were right, then someone could really cause havoc around the world.
You're not right, though.

Excellent point, well made. It's easy to forget java is used on the
client side, too.

Matt
 
C

Chris Smith

Matt Rose said:
Does this get checked by the GETFIELD opcode? I can't find any
indication that it does in the VM spec but I haven't tested it. I never
normally get involved with bytecode.

The getfield opcode is checked by the bytecode verifier when the class
is loaded. If it tries to access a field to which it doesn't have
access permission, the verifier will throw a java.lang.VerifyError, and
the class will fail to load.

This has nothing to do with ReflectPermission("suppressAccessChecks");
it just is. If you want to suppress access checks, you can't do it with
direct bytecode instructions to access the hidden methods and fields.
Even if you were to call setAccessible, you still couldn't load bytecode
that tries to access a private field directly. You would need to use
the Reflection API to do that.

As long as we're on the topic, it should be mentioned that the
discussion of private fields might be misleading. From a security
standpoint, private is actually considerably less interesting than
package-access restrictions. The Java compiler routinely generates
hidden package-access methods that allow access to private fields
(particularly when dealing with nested classes), so relying on private
access is not safe. Building a secure API requires placing classes in a
sealed package, and then ensuring that all protected or public access
meets the security requirements. Since the core API packages are
sealed, this is exactly what happens here.
 
B

Babu Kalakrishnan

Chris said:
As long as we're on the topic, it should be mentioned that the
discussion of private fields might be misleading. From a security
standpoint, private is actually considerably less interesting than
package-access restrictions. The Java compiler routinely generates
hidden package-access methods that allow access to private fields
(particularly when dealing with nested classes), so relying on private
access is not safe. Building a secure API requires placing classes in a
sealed package, and then ensuring that all protected or public access
meets the security requirements. Since the core API packages are
sealed, this is exactly what happens here.

One query in connection with that last statement. Remember seeing an
article a while back about how easy it is to crack an encrypted
classloader by replacing one of the classes in rt.jar with a modified
version (the ClassLoader class). If the rt.jar file is indeed sealed,
would this be allowed ? (Isn't there some restriction that every class
in a package must be signed by the same signer?)

Also, is there a way for an application program to determine if it is
indeed running under a JVM whose core library is sealed ? (I remember
trying to call the getSigners() method on ClassLoader.class to check
this out, but if my memory serves me right, it returned null in a JDK1.4
runtime).

The article I am referring to is :

http://www.javaworld.com/javaworld/javaqa/2003-05/01-qa-0509-jcrypt.html

BK
 
C

Chris Smith

Babu Kalakrishnan said:
One query in connection with that last statement. Remember seeing an
article a while back about how easy it is to crack an encrypted
classloader by replacing one of the classes in rt.jar with a modified
version (the ClassLoader class). If the rt.jar file is indeed sealed,
would this be allowed ?

It is allowed because you are assuming the ability to replace rt.jar.
All of the system packages in rt.jar are sealed, but you can replace
rt.jar with a different JAR file where those packages are not sealed.
Hostile code running within the JVM would not have the ability to
replace the rt.jar file.

The article you quote also mentions JVMPI, which is since superceded by
JVMTI. These APIs also work, but again are not available to hostile
code running under a SecurityManager.
(Isn't there some restriction that every class
in a package must be signed by the same signer?)

Nope. All that's required is that the line "Sealed: true" in the JAR
manifest. Package signing is a different matter. If the JVM loads a
class from a JAR file with "Sealed: true" in the manifest, then it will
ensure that it does not (and has not, in the past) load a class in the
same package from a different JAR file.
Also, is there a way for an application program to determine if it is
indeed running under a JVM whose core library is sealed?

If you have a Class object, you can call myClass.getPackage().isSealed()
to find out. I don't know of a place where that doesn't work. The API
docs are missing @since tags (infuriatingly common), but I'm sure it's
been around for at least a couple major versions now (1.4 and 1.5, at a
minimum; possibly as old as 1.2).
 
B

Babu Kalakrishnan

Chris said:
Nope. All that's required is that the line "Sealed: true" in the JAR
manifest. Package signing is a different matter. If the JVM loads a
class from a JAR file with "Sealed: true" in the manifest, then it will
ensure that it does not (and has not, in the past) load a class in the
same package from a different JAR file.

Thanks, I was confused between "Sealed" and "Signed".

Interestingly, I just looked at the manifest.mf file present in the
rt.jar of my JDK distribution - and it doesn't even seem to have a
"Sealed: true" attribute present.

Is this because the java.* package hierarchy is "sealed" through
extraneous means (by allowing loading only through the bootstrap
classloader) and Sun wants users to be able to replace files in the
javax hierarchy ?


BK
 
T

Thomas Hawtin

Chris said:
Nope. All that's required is that the line "Sealed: true" in the JAR
manifest. Package signing is a different matter. If the JVM loads a
class from a JAR file with "Sealed: true" in the manifest, then it will
ensure that it does not (and has not, in the past) load a class in the
same package from a different JAR file.

Assuming a package is not sealed, you can have other class loaders load
classes into the same package namespace. However, those classes will not
have package access rights to the original package. A class loader also
does not allow classes to be defined in the same package (with the same
class loader) with different certificates.

Tom Hawtin
 
C

Chris Uppal

Chris Smith said:
The getfield opcode is checked by the bytecode verifier when the class
is loaded. If it tries to access a field to which it doesn't have
access permission, the verifier will throw a java.lang.VerifyError, and
the class will fail to load.

The picture is more complicated than that. For a start, I don't think that
it's the verifier's job to enforce access checks -- they shouldn't fail until
an illegal access is actually executed.

Secondly, the rules for when the JVM will actually enforce access seem to be
somewhat obscure. I know that it will /sometimes/ do checks, but not always.
And what "sometimes" means has changed over the years.

For instance. With JDK 1.5.0_06, compile these two files

========== A.java =================
public class A
{
// NB: deliberately public, despite the name
public String privateField = "Ooops!";
}
=========== B.java ================
public class B
{
public static void
main(String[] args)
{
A a = new A();
System.out.println("OK, here we go...");
System.out.println(a.privateField);
}
}
=================================

Then change the declaration of A.privateField to private, and recompile only
A.java. (All this messing around is just a way to generate bytecode containing
an "illegal" read -- people with convenient access to bytecode generation will
have more straightforward ways of doing it).

Then execute:

java -cp . B

and it prints:

OK, here we go...
Ooops!

OTOH, running java with the "future" argument:

java -Xfuture -cp . B

and it prints:

OK, here we go...
Exception in thread "main" java.lang.IllegalAccessError: tried to access
field A.privateField from class B
at B.main(B.java:8)


On the third hand, even without the -Xfuture argument, the current JVM will
(iirc) stop you accessing the internal fields of a String object. I'm not sure
on what basis it determines that some accesses should be checked and others
not.

As far as I know, /all/ such access should result in the JVM throwing runtime
exceptions, but Sun's JVM's seem to interpret that aspect of the spec a little
loosely...

(And anyway, there's always JNI ;-)

-- chris
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,602
Members
45,182
Latest member
BettinaPol

Latest Threads

Top