Java字符串不是真正不可变的含义是什么?

chi*_*ity 6 java security string clone immutability

背景

在Java 101中,我们被教导:

A String是不可改变的.

是.好.谢谢.

然后我们到达Java 102(或者Java 201),我们发现:

A String不是真正不可变的:你可以使用反射来改变它.

啊.精细.根据你的观点,要么非常可爱,要么无法估量.

到目前为止,这些事情已经在Stack Overflow和其他地方无限讨论.在构思这个问题时,我认为这是理所当然的.

我有兴趣问的是:

这个问题

一旦我们发现a String不是真正不可变的,那么我们应该如何编写代码有什么意义呢?

让我在两个方面更具体.

JVM沙盒

恶意类是否有可能使用此技巧来破坏JVM的安全性,并采取不应该使用的技巧?例如,JDK中是否存在String从方法返回的位置,并且只有在无法更改的情况下才安全?

这是一个很有希望的开始:

    String prop = "java.version";
    // retrieve a System property as a String
    String s = System.getProperty(prop);
    System.out.println(s);
    // now mess with it
    Field field = String.class.getDeclaredField("value");  
    field.setAccessible(true);  
    char[] value = (char[])field.get(s);  
    value[0] = 'x';
    //turns out we've changed not just the String we were
    //given but the underlying property too!
    System.out.println(System.getProperty(prop));
Run Code Online (Sandbox Code Playgroud)

这样做的目的是从JVM中检索一个系统属性String,然后改变它String; 结果是基础财产也发生了变化.这可以用来造成严重破坏吗?

我不确定.值得注意的是,您必须拥有执行反射的权限.那个安全游戏已经上升了吗?它允许我在这里没有更改安全属性的权限,但这是预期的,还是一个问题?

有没有办法扩展这个做更糟糕的事情?

防守克隆

我们总是被告知,在将数组和其他对象传递给可能修改它们的方法之前克隆它们是个好主意,除非我们希望它们被修改.这只是很好的编码实践.如果你给某人一个阵列的唯一副本并且它回来搞砸了,这是你自己的愚蠢错误.

但看起来同样的论点应该适用于String!我从来没有听过有人说我们应该克隆一个,String然后再把它传递给别人写的方法.但是这有什么不同,如果a String像阵列一样可变吗?

支持防守克隆字符串的争论

如果防御性克隆完全不知道方法可以对我们传递的东西做什么,如果我们传递的东西是可变的,那么我们应该克隆它.如果代码可能是恶意的,那么它可能会改变一个String; 所以每当重要的是String保持不变时,我们应该确保发送副本而不是真实的副本.

如果我们不相信它不会做坏事,那么不应该说不受信任的代码应该在安全管理器下运行String.如果对于a String来说这是真的,对阵列来说就是如此; 但是没有人说只有在你用安全管理器锁定代码的情况下才能克隆数组和其他对象.

反对防守克隆字符串的争论

一个数组可能只是通过草率编码来修改; 换句话说,它可能会被无意中修改.但是没有人String无意中改变:只能用偷偷摸摸的技巧来表达程序员试图打破不变性.

这使得两种情况不同.我们真的不应该将数组(甚至是它的克隆)传递给我们在任何级别上都不信任的代码.你没有从某个地方下载一个库,然后认为你没事,因为你克隆了你的数组.你在防御性方面进行克隆,因为你认为程序员可能会犯错误或对你发送的数据做出不同的假设.如果您担心代码可能会在String后面修改s,那么您根本不应该运行代码.通过克隆来实现的String只是性能开销.

答案?

我们该怎么想呢?使用这些技巧可以打破JVM沙盒吗?我们是否应该根据a的可变性进行防御性编码String,或者这都是红鲱鱼?

rge*_*man 4

有一个可以为您的 Java 程序启用的安全设置。根据Javadocs 的说法setAccessible

首先,如果有安全管理器,则使用 ReflectPermission("suppressAccessChecks") 权限调用其 checkPermission 方法。

如果 flag 为 true,则引发 SecurityException,但不能更改此对象的可访问性(例如,如果此元素对象是类 Class 的构造函数对象)。

如果此对象是类 java.lang.Class 的构造函数对象,并且 flag 为 true,则会引发 SecurityException。

因此,如果 aSecurityManager不允许进行此检查,您可以阻止任何代码成功调用setAccessible,从而保留 s 的不变性String